-
Notifications
You must be signed in to change notification settings - Fork 753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Debug Flag for Cookie Sync #3107
Changes from all commits
53dd683
6cd2272
ae6259c
2d5afdc
0cf88cd
2541956
ff41f45
cb33792
96caaa9
5ffbc7a
e725769
22a7777
2b837c6
fdf26b4
008db67
7c1759e
b000c9e
f4a38bf
462295a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,7 +61,7 @@ func NewCookieSyncEndpoint( | |
} | ||
|
||
return &cookieSyncEndpoint{ | ||
chooser: usersync.NewChooser(syncersByBidder), | ||
chooser: usersync.NewChooser(syncersByBidder, bidderHashSet), | ||
config: config, | ||
privacyConfig: usersyncPrivacyConfig{ | ||
gdprConfig: config.GDPR, | ||
|
@@ -98,17 +98,18 @@ func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ ht | |
usersync.SyncHostCookie(r, cookie, &c.config.HostCookie) | ||
|
||
result := c.chooser.Choose(request, cookie) | ||
|
||
switch result.Status { | ||
case usersync.StatusBlockedByUserOptOut: | ||
c.metrics.RecordCookieSync(metrics.CookieSyncOptOut) | ||
c.handleError(w, errCookieSyncOptOut, http.StatusUnauthorized) | ||
case usersync.StatusBlockedByGDPR: | ||
case usersync.StatusBlockedByPrivacy: | ||
c.metrics.RecordCookieSync(metrics.CookieSyncGDPRHostCookieBlocked) | ||
c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil) | ||
c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil, result.BiddersEvaluated, request.Debug) | ||
case usersync.StatusOK: | ||
c.metrics.RecordCookieSync(metrics.CookieSyncOK) | ||
c.writeSyncerMetrics(result.BiddersEvaluated) | ||
c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen) | ||
c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen, result.BiddersEvaluated, request.Debug) | ||
} | ||
} | ||
|
||
|
@@ -172,6 +173,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma | |
Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault), | ||
PriorityGroups: c.config.UserSync.PriorityGroups, | ||
}, | ||
Debug: request.Debug, | ||
Limit: request.Limit, | ||
Privacy: usersyncPrivacy{ | ||
gdprPermissions: gdprPerms, | ||
|
@@ -387,9 +389,7 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd | |
switch bidder.Status { | ||
case usersync.StatusOK: | ||
c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncOK) | ||
case usersync.StatusBlockedByGDPR: | ||
c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) | ||
case usersync.StatusBlockedByCCPA: | ||
case usersync.StatusBlockedByPrivacy: | ||
c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) | ||
case usersync.StatusAlreadySynced: | ||
c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncAlreadySynced) | ||
|
@@ -399,7 +399,7 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd | |
} | ||
} | ||
|
||
func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice) { | ||
func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice, biddersEvaluated []usersync.BidderEvaluation, debug bool) { | ||
status := "no_cookie" | ||
if co.HasAnyLiveSyncs() { | ||
status = "ok" | ||
|
@@ -429,6 +429,24 @@ func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.S | |
}) | ||
} | ||
|
||
if debug { | ||
biddersSeen := make(map[string]struct{}) | ||
var debugInfo []cookieSyncResponseDebug | ||
for _, bidderEval := range biddersEvaluated { | ||
var debugResponse cookieSyncResponseDebug | ||
debugResponse.Bidder = bidderEval.Bidder | ||
if bidderEval.Status == usersync.StatusDuplicate && biddersSeen[bidderEval.Bidder] == struct{}{} { | ||
debugResponse.Error = getDebugMessage(bidderEval.Status) + " synced as " + bidderEval.SyncerKey | ||
debugInfo = append(debugInfo, debugResponse) | ||
} else if bidderEval.Status != usersync.StatusOK { | ||
debugResponse.Error = getDebugMessage(bidderEval.Status) | ||
debugInfo = append(debugInfo, debugResponse) | ||
} | ||
biddersSeen[bidderEval.Bidder] = struct{}{} | ||
} | ||
response.Debug = debugInfo | ||
} | ||
|
||
c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ | ||
Status: http.StatusOK, | ||
BidderStatus: mapBidderStatusToAnalytics(response.BidderStatus), | ||
|
@@ -456,6 +474,26 @@ func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.Co | |
return to | ||
} | ||
|
||
func getDebugMessage(status usersync.Status) string { | ||
switch status { | ||
case usersync.StatusAlreadySynced: | ||
return "Already in sync" | ||
case usersync.StatusBlockedByPrivacy: | ||
return "Rejected by privacy" | ||
case usersync.StatusBlockedByUserOptOut: | ||
return "Status blocked by user opt out" | ||
case usersync.StatusDuplicate: | ||
return "Duplicate bidder" | ||
Comment on lines
+485
to
+486
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this equivalent to PBS-Java's "synced as FAMILY" status or is there a difference here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this is the equivalent, and it'll have appended to it: |
||
case usersync.StatusUnknownBidder: | ||
return "Unsupported bidder" | ||
case usersync.StatusUnconfiguredBidder: | ||
return "No sync config" | ||
case usersync.StatusTypeNotSupported: | ||
return "Type not supported" | ||
} | ||
return "" | ||
} | ||
|
||
type cookieSyncRequest struct { | ||
Bidders []string `json:"bidders"` | ||
GDPR *int `json:"gdpr"` | ||
|
@@ -467,6 +505,7 @@ type cookieSyncRequest struct { | |
CooperativeSync *bool `json:"coopSync"` | ||
FilterSettings *cookieSyncRequestFilterSettings `json:"filterSettings"` | ||
Account string `json:"account"` | ||
Debug bool `json:"debug"` | ||
} | ||
|
||
type cookieSyncRequestFilterSettings struct { | ||
|
@@ -482,6 +521,7 @@ type cookieSyncRequestFilter struct { | |
type cookieSyncResponse struct { | ||
Status string `json:"status"` | ||
BidderStatus []cookieSyncResponseBidder `json:"bidder_status"` | ||
Debug []cookieSyncResponseDebug `json:"debug,omitempty"` | ||
} | ||
|
||
type cookieSyncResponseBidder struct { | ||
|
@@ -496,6 +536,11 @@ type cookieSyncResponseSync struct { | |
SupportCORS bool `json:"supportCORS,omitempty"` | ||
} | ||
|
||
type cookieSyncResponseDebug struct { | ||
Bidder string `json:"bidder"` | ||
Error string `json:"error,omitempty"` | ||
} | ||
|
||
type usersyncPrivacyConfig struct { | ||
gdprConfig config.GDPR | ||
gdprPermissionsBuilder gdpr.PermissionsBuilder | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,8 +14,9 @@ type Chooser interface { | |
} | ||
|
||
// NewChooser returns a new instance of the standard chooser implementation. | ||
func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { | ||
func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]struct{}) Chooser { | ||
bidders := make([]string, 0, len(bidderSyncerLookup)) | ||
|
||
for k := range bidderSyncerLookup { | ||
bidders = append(bidders, k) | ||
} | ||
|
@@ -25,6 +26,7 @@ func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { | |
biddersAvailable: bidders, | ||
bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, | ||
normalizeValidBidderName: openrtb_ext.NormalizeBidderName, | ||
biddersKnown: biddersKnown, | ||
} | ||
} | ||
|
||
|
@@ -35,6 +37,7 @@ type Request struct { | |
Limit int | ||
Privacy Privacy | ||
SyncTypeFilter SyncTypeFilter | ||
Debug bool | ||
} | ||
|
||
// Cooperative specifies the settings for cooperative syncing for a given request, where bidders | ||
|
@@ -74,13 +77,6 @@ const ( | |
// StatusBlockedByUserOptOut specifies a user's cookie explicitly signals an opt-out. | ||
StatusBlockedByUserOptOut | ||
|
||
// StatusBlockedByGDPR specifies a user's GDPR TCF consent explicitly forbids host cookies | ||
// or specific bidder syncing. | ||
StatusBlockedByGDPR | ||
|
||
// StatusBlockedByCCPA specifies a user's CCPA consent explicitly forbids bidder syncing. | ||
StatusBlockedByCCPA | ||
|
||
// StatusAlreadySynced specifies a user's cookie has an existing non-expired sync for a specific bidder. | ||
StatusAlreadySynced | ||
|
||
|
@@ -95,6 +91,9 @@ const ( | |
|
||
// StatusBlockedByPrivacy specifies a bidder sync url is not allowed by privacy activities | ||
StatusBlockedByPrivacy | ||
|
||
// StatusUnconfiguredBidder refers to a bidder who hasn't been configured to have a syncer key, but is known by Prebid Server | ||
StatusUnconfiguredBidder | ||
AlexBVolcy marked this conversation as resolved.
Show resolved
Hide resolved
AlexBVolcy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
// Privacy determines which privacy policies will be enforced for a user sync request. | ||
|
@@ -111,6 +110,7 @@ type standardChooser struct { | |
biddersAvailable []string | ||
bidderChooser bidderChooser | ||
normalizeValidBidderName func(name string) (openrtb_ext.BidderName, bool) | ||
biddersKnown map[string]struct{} | ||
} | ||
|
||
// Choose randomly selects user syncers which are permitted by the user's privacy settings and | ||
|
@@ -121,23 +121,28 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { | |
} | ||
|
||
if !request.Privacy.GDPRAllowsHostCookie() { | ||
return Result{Status: StatusBlockedByGDPR} | ||
return Result{Status: StatusBlockedByPrivacy} | ||
} | ||
|
||
syncersSeen := make(map[string]struct{}) | ||
biddersSeen := make(map[string]struct{}) | ||
limitDisabled := request.Limit <= 0 | ||
|
||
biddersEvaluated := make([]BidderEvaluation, 0) | ||
syncersChosen := make([]SyncerChoice, 0) | ||
|
||
bidders := c.bidderChooser.choose(request.Bidders, c.biddersAvailable, request.Cooperative) | ||
for i := 0; i < len(bidders) && (limitDisabled || len(syncersChosen) < request.Limit); i++ { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is necessary, but I see that PBS-Java provides debug information indicating when a bidder is ignored because the limit has been reached. Similarly they are indicating if a bidder is known but disabled. We should discuss whether we want to add this logic, perhaps in a separate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh interesting, yeah let's talk about this at the next team time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline. PBS-Go behavior is different from PBS-Java. We stop processing when we reach the limit so there's no need to report on this. |
||
if _, ok := biddersSeen[bidders[i]]; ok { | ||
continue | ||
} | ||
syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie) | ||
|
||
biddersEvaluated = append(biddersEvaluated, evaluation) | ||
if evaluation.Status == StatusOK { | ||
syncersChosen = append(syncersChosen, SyncerChoice{Bidder: bidders[i], Syncer: syncer}) | ||
} | ||
biddersSeen[bidders[i]] = struct{}{} | ||
} | ||
|
||
return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} | ||
|
@@ -151,7 +156,11 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} | |
|
||
AlexBVolcy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
syncer, exists := c.bidderSyncerLookup[bidderNormalized.String()] | ||
if !exists { | ||
return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} | ||
if _, ok := c.biddersKnown[bidder]; !ok { | ||
return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} | ||
} else { | ||
return nil, BidderEvaluation{Status: StatusUnconfiguredBidder, Bidder: bidder} | ||
} | ||
} | ||
|
||
_, seen := syncersSeen[syncer.Key()] | ||
|
@@ -174,11 +183,7 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} | |
} | ||
|
||
if !privacy.GDPRAllowsBidderSync(bidderNormalized.String()) { | ||
return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} | ||
} | ||
|
||
if !privacy.CCPAAllowsBidderSync(bidderNormalized.String()) { | ||
return nil, BidderEvaluation{Status: StatusBlockedByCCPA, Bidder: bidder, SyncerKey: syncer.Key()} | ||
return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} | ||
} | ||
|
||
return syncer, BidderEvaluation{Status: StatusOK, Bidder: bidder, SyncerKey: syncer.Key()} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious what's driving the change from regulation-specific statuses to a more generic privacy status. Was there a discussion about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scott and I did have a conversation a while ago and this name change was brought up I think in conjunction with removing CCPA from the
evaluate()
function. @SyntaxNode I think could touch on this more.