Repository: prebid/prebid-server
Pull request #3623 (2024-04-11 16:55:31)

Metrics: Add per adapter metric indicating when buyeruid was scrubbed


Patch:
diff --git a/config/config.go b/config/config.go
index 86521c4c..d2f21039 100644
--- a/config/config.go
+++ b/config/config.go
@@ -560,6 +560,9 @@ type DisabledMetrics struct {
 	// that were created or reused.
 	AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"`
 
+	// True if we don't want to collect the per adapter buyer UID scrubbed metric
+	AdapterBuyerUIDScrubbed bool `mapstructure:"adapter_buyeruid_scrubbed"`
+
 	// True if we don't want to collect the per adapter GDPR request blocked metric
 	AdapterGDPRRequestBlocked bool `mapstructure:"adapter_gdpr_request_blocked"`
 
@@ -945,6 +948,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) {
 	v.SetDefault("metrics.disabled_metrics.account_debug", true)
 	v.SetDefault("metrics.disabled_metrics.account_stored_responses", true)
 	v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true)
+	v.SetDefault("metrics.disabled_metrics.adapter_buyeruid_scrubbed", true)
 	v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false)
 	v.SetDefault("metrics.influxdb.host", "")
 	v.SetDefault("metrics.influxdb.database", "")
diff --git a/exchange/utils.go b/exchange/utils.go
index 671e2ff8..7d2e47c2 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -200,20 +200,27 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
 		}
 
 		passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req))
+		buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != ""
+		buyerUIDRemoved := false
 		if !passIDActivityAllowed {
-			//UFPD
 			privacy.ScrubUserFPD(reqWrapper)
+			buyerUIDRemoved = true
 		} else {
 			// run existing policies (GDPR, CCPA, COPPA, LMT)
 			// potentially block passing IDs based on GDPR
 			if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassID) {
 				privacy.ScrubGdprID(reqWrapper)
+				buyerUIDRemoved = true
 			}
 			// potentially block passing IDs based on CCPA
 			if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
 				privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
+				buyerUIDRemoved = true
 			}
 		}
+		if buyerUIDSet && buyerUIDRemoved {
+			rs.me.RecordAdapterBuyerUIDScrubbed(bidderRequest.BidderCoreName)
+		}
 
 		passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req))
 		if !passGeoActivityAllowed {
diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go
index 7ed38751..8b544bd9 100644
--- a/metrics/config/metrics.go
+++ b/metrics/config/metrics.go
@@ -266,6 +266,13 @@ func (me *MultiMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels
 	}
 }
 
+// RecordAdapterBuyerUIDScrubbed across all engines
+func (me *MultiMetricsEngine) RecordAdapterBuyerUIDScrubbed(adapter openrtb_ext.BidderName) {
+	for _, thisME := range *me {
+		thisME.RecordAdapterBuyerUIDScrubbed(adapter)
+	}
+}
+
 // RecordAdapterGDPRRequestBlocked across all engines
 func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) {
 	for _, thisME := range *me {
@@ -484,6 +491,10 @@ func (me *NilMetricsEngine) RecordTimeoutNotice(success bool) {
 func (me *NilMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) {
 }
 
+// RecordAdapterBuyerUIDScrubbed as a noop
+func (me *NilMetricsEngine) RecordAdapterBuyerUIDScrubbed(adapter openrtb_ext.BidderName) {
+}
+
 // RecordAdapterGDPRRequestBlocked as a noop
 func (me *NilMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) {
 }
diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go
index 5bc4ab96..ac28a18c 100644
--- a/metrics/go_metrics.go
+++ b/metrics/go_metrics.go
@@ -99,6 +99,7 @@ type AdapterMetrics struct {
 	ConnCreated        metrics.Counter
 	ConnReused         metrics.Counter
 	ConnWaitTime       metrics.Timer
+	BuyerUIDScrubbed   metrics.Meter
 	GDPRRequestBlocked metrics.Meter
 
 	BidValidationCreativeSizeErrorMeter metrics.Meter
@@ -406,6 +407,9 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet
 		newAdapter.ConnReused = metrics.NilCounter{}
 		newAdapter.ConnWaitTime = &metrics.NilTimer{}
 	}
+	if !disabledMetrics.AdapterBuyerUIDScrubbed {
+		newAdapter.BuyerUIDScrubbed = blankMeter
+	}
 	if !disabledMetrics.AdapterGDPRRequestBlocked {
 		newAdapter.GDPRRequestBlocked = blankMeter
 	}
@@ -484,6 +488,7 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string,
 		am.BidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.bids_received", adapterOrAccount, exchange), registry)
 	}
 	am.PanicMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.requests.panic", adapterOrAccount, exchange), registry)
+	am.BuyerUIDScrubbed = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.buyeruid_scrubbed", adapterOrAccount, exchange), registry)
 	am.GDPRRequestBlocked = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.gdpr_request_blocked", adapterOrAccount, exchange), registry)
 
 	am.BidValidationCreativeSizeErrorMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.response.validation.size.err", adapterOrAccount, exchange), registry)
@@ -926,6 +931,21 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) {
 	}
 }
 
+func (me *Metrics) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) {
+	adapterStr := adapterName.String()
+	if me.MetricsDisabled.AdapterBuyerUIDScrubbed {
+		return
+	}
+
+	am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)]
+	if !ok {
+		glog.Errorf("Trying to log adapter buyeruid scrubbed metric for %s: adapter not found", adapterStr)
+		return
+	}
+
+	am.BuyerUIDScrubbed.Mark(1)
+}
+
 func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
 	adapterStr := string(adapterName)
 	if me.MetricsDisabled.AdapterGDPRRequestBlocked {
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 7d3dc819..2696c642 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -455,6 +455,7 @@ type MetricsEngine interface {
 	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
 	RecordTimeoutNotice(success bool)
 	RecordRequestPrivacy(privacy PrivacyLabels)
+	RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName)
 	RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName)
 	RecordDebugRequest(debugEnabled bool, pubId string)
 	RecordStoredResponse(pubId string)
diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go
index ef9fdcaf..56e2ab8f 100644
--- a/metrics/metrics_mock.go
+++ b/metrics/metrics_mock.go
@@ -156,6 +156,11 @@ func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) {
 	me.Called(privacy)
 }
 
+// RecordAdapterBuyerUIDScrubbed mock
+func (me *MetricsEngineMock) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) {
+	me.Called(adapterName)
+}
+
 // RecordAdapterGDPRRequestBlocked mock
 func (me *MetricsEngineMock) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
 	me.Called(adapterName)
diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go
index a4a70017..73ea6437 100644
--- a/metrics/prometheus/preload.go
+++ b/metrics/prometheus/preload.go
@@ -229,6 +229,12 @@ func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[st
 		versionLabel: tcfVersionValues,
 	})
 
+	if !m.metricsDisabled.AdapterBuyerUIDScrubbed {
+		preloadLabelValuesForCounter(m.adapterScrubbedBuyerUIDs, map[string][]string{
+			adapterLabel: adapterValues,
+		})
+	}
+
 	if !m.metricsDisabled.AdapterGDPRRequestBlocked {
 		preloadLabelValuesForCounter(m.adapterGDPRBlockedRequests, map[string][]string{
 			adapterLabel: adapterValues,
diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go
index 6bd30544..89b589ba 100644
--- a/metrics/prometheus/prometheus.go
+++ b/metrics/prometheus/prometheus.go
@@ -70,6 +70,7 @@ type Metrics struct {
 	adapterReusedConnections              *prometheus.CounterVec
 	adapterCreatedConnections             *prometheus.CounterVec
 	adapterConnectionWaitTime             *prometheus.HistogramVec
+	adapterScrubbedBuyerUIDs              *prometheus.CounterVec
 	adapterGDPRBlockedRequests            *prometheus.CounterVec
 	adapterBidResponseValidationSizeError *prometheus.CounterVec
 	adapterBidResponseValidationSizeWarn  *prometheus.CounterVec
@@ -334,6 +335,12 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
 		"Count of total requests to Prebid Server where the LMT flag was set by source",
 		[]string{sourceLabel})
 
+	if !metrics.metricsDisabled.AdapterBuyerUIDScrubbed {
+		metrics.adapterScrubbedBuyerUIDs = newCounter(cfg, reg,
+			"adapter_buyeruids_scrubbed",
+			"Count of total bidder requests with a scrubbed buyeruid due to a privacy policy",
+			[]string{adapterLabel})
+	}
 	if !metrics.metricsDisabled.AdapterGDPRRequestBlocked {
 		metrics.adapterGDPRBlockedRequests = newCounter(cfg, reg,
 			"adapter_gdpr_requests_blocked",
@@ -952,6 +959,16 @@ func (m *Metrics) RecordRequestPrivacy(privacy metrics.PrivacyLabels) {
 	}
 }
 
+func (m *Metrics) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) {
+	if m.metricsDisabled.AdapterBuyerUIDScrubbed {
+		return
+	}
+
+	m.adapterScrubbedBuyerUIDs.With(prometheus.Labels{
+		adapterLabel: strings.ToLower(string(adapterName)),
+	}).Inc()
+}
+
 func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
 	if m.metricsDisabled.AdapterGDPRRequestBlocked {
 		return
diff --git a/static/bidder-info/axonix.yaml b/static/bidder-info/axonix.yaml
index 8bb68a4b..9744e064 100644
--- a/static/bidder-info/axonix.yaml
+++ b/static/bidder-info/axonix.yaml
@@ -14,3 +14,7 @@ capabilities:
       - banner
       - video
       - native
+userSync:
+  redirect:
+    url: "https://openrtb-us-east-1.axonix.com/syn?redirect={{.RedirectURL}}"
+    userMacro: xxEMODO_IDxx
diff --git a/static/bidder-info/sharethrough.yaml b/static/bidder-info/sharethrough.yaml
index 0b3479f5..9ee8cb91 100644
--- a/static/bidder-info/sharethrough.yaml
+++ b/static/bidder-info/sharethrough.yaml
@@ -15,5 +15,5 @@ capabilities:
       - native
 userSync:
   redirect:
-    url: https://match.sharethrough.com/FGMrCMMc/v1?redirectUri={{.RedirectURL}}
+    url: https://match.sharethrough.com/FGMrCMMc/v1?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirectUri={{.RedirectURL}}
     userMacro: $UID


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
index 4c97f125..4a553e41 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -158,6 +158,7 @@ func TestDefaults(t *testing.T) {
 	cmpBools(t, "account_debug", true, cfg.Metrics.Disabled.AccountDebug)
 	cmpBools(t, "account_stored_responses", true, cfg.Metrics.Disabled.AccountStoredResponses)
 	cmpBools(t, "adapter_connections_metrics", true, cfg.Metrics.Disabled.AdapterConnectionMetrics)
+	cmpBools(t, "adapter_buyeruid_scrubbed", true, cfg.Metrics.Disabled.AdapterBuyerUIDScrubbed)
 	cmpBools(t, "adapter_gdpr_request_blocked", false, cfg.Metrics.Disabled.AdapterGDPRRequestBlocked)
 	cmpStrings(t, "certificates_file", "", cfg.PemCertsFile)
 	cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled)
@@ -445,6 +446,7 @@ metrics:
     account_debug: false
     account_stored_responses: false
     adapter_connections_metrics: true
+    adapter_buyeruid_scrubbed: false
     adapter_gdpr_request_blocked: true
     account_modules_metrics: true
 blacklisted_apps: ["spamAppID","sketchy-app-id"]
@@ -858,6 +860,7 @@ func TestFullConfig(t *testing.T) {
 	cmpBools(t, "account_debug", false, cfg.Metrics.Disabled.AccountDebug)
 	cmpBools(t, "account_stored_responses", false, cfg.Metrics.Disabled.AccountStoredResponses)
 	cmpBools(t, "adapter_connections_metrics", true, cfg.Metrics.Disabled.AdapterConnectionMetrics)
+	cmpBools(t, "adapter_buyeruid_scrubbed", false, cfg.Metrics.Disabled.AdapterBuyerUIDScrubbed)
 	cmpBools(t, "adapter_gdpr_request_blocked", true, cfg.Metrics.Disabled.AdapterGDPRRequestBlocked)
 	cmpStrings(t, "certificates_file", "/etc/ssl/cert.pem", cfg.PemCertsFile)
 	cmpStrings(t, "request_validation.ipv4_private_networks", "1.1.1.0/24", cfg.RequestValidation.IPv4PrivateNetworks[0])
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 3b723d4e..4ca13c52 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -1015,10 +1015,13 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 			},
 		}.Builder
 
+		metricsMock := metrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()
+
 		bidderToSyncerKey := map[string]string{}
 		reqSplitter := &requestSplitter{
 			bidderToSyncerKey: bidderToSyncerKey,
-			me:                &metrics.MetricsEngineMock{},
+			me:                &metricsMock,
 			privacyConfig:     privacyConfig,
 			gdprPermsBuilder:  gdprPermissionsBuilder,
 			hostSChainNode:    nil,
@@ -1032,9 +1035,11 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 		if test.expectDataScrub {
 			assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		} else {
 			assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		}
 		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
 	}
@@ -2141,9 +2146,12 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprDefaultValue = gdpr.SignalNo
 		}
 
+		metricsMock := metrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()
+
 		reqSplitter := &requestSplitter{
 			bidderToSyncerKey: map[string]string{},
-			me:                &metrics.MetricsEngineMock{},
+			me:                &metricsMock,
 			privacyConfig:     privacyConfig,
 			gdprPermsBuilder:  gdprPermissionsBuilder,
 			hostSChainNode:    nil,
@@ -2162,9 +2170,11 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 		if test.gdprScrub {
 			assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		} else {
 			assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		}
 		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
 	}
@@ -4523,6 +4533,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 		allow             bool
 		expectedReqNumber int
 		expectedUser      openrtb2.User
+		expectUserScrub   bool
 		expectedDevice    openrtb2.Device
 		expectedSource    openrtb2.Source
 		expectedImpExt    json.RawMessage
@@ -4570,6 +4581,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 				Ext:      json.RawMessage(`{"test":2}`),
 				Data:     nil,
 			},
+			expectUserScrub: true,
 			expectedDevice: openrtb2.Device{
 				UA:       deviceUA,
 				Language: "EN",
@@ -4670,10 +4682,13 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 				TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
 			}
 
+			metricsMock := metrics.MetricsEngineMock{}
+			metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()
+
 			bidderToSyncerKey := map[string]string{}
 			reqSplitter := &requestSplitter{
 				bidderToSyncerKey: bidderToSyncerKey,
-				me:                &metrics.MetricsEngineMock{},
+				me:                &metricsMock,
 				hostSChainNode:    nil,
 				bidderInfo:        config.BidderInfos{},
 			}
@@ -4690,6 +4705,11 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 				if len(test.expectedImpExt) > 0 {
 					assert.JSONEq(t, string(test.expectedImpExt), string(bidderRequests[0].BidRequest.Imp[0].Ext))
 				}
+				if test.expectUserScrub {
+					metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
+				} else {
+					metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
+				}
 			}
 		})
 	}
diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go
index 5badc348..e201cf52 100644
--- a/metrics/config/metrics_test.go
+++ b/metrics/config/metrics_test.go
@@ -143,6 +143,7 @@ func TestMultiMetricsEngine(t *testing.T) {
 	metricsEngine.RecordStoredImpCacheResult(metrics.CacheHit, 5)
 	metricsEngine.RecordAccountCacheResult(metrics.CacheHit, 6)
 
+	metricsEngine.RecordAdapterBuyerUIDScrubbed(openrtb_ext.BidderAppnexus)
 	metricsEngine.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus)
 
 	metricsEngine.RecordRequestQueueTime(false, metrics.ReqTypeVideo, time.Duration(1))
@@ -188,6 +189,7 @@ func TestMultiMetricsEngine(t *testing.T) {
 	VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5)
 	VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6)
 
+	VerifyMetrics(t, "AdapterMetrics.appNexus.BuyerUIDScrubbed", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].BuyerUIDScrubbed.Count(), 1)
 	VerifyMetrics(t, "AdapterMetrics.appNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GDPRRequestBlocked.Count(), 1)
 
 	// verify that each module has its own metric recorded
diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go
index 05529220..7cacd909 100644
--- a/metrics/go_metrics_test.go
+++ b/metrics/go_metrics_test.go
@@ -790,43 +790,87 @@ func TestRecordRequestPrivacy(t *testing.T) {
 	assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2")
 }
 
+func TestRecordAdapterBuyerUIDScrubbed(t *testing.T) {
+	var fakeBidder openrtb_ext.BidderName = "fooAdvertising"
+	adapter := "AnyName"
+	lowerCaseAdapterName := "anyname"
+
+	tests := []struct {
+		name            string
+		metricsDisabled bool
+		adapterName     openrtb_ext.BidderName
+		expectedCount   int64
+	}{
+		{
+			name:            "enabled_bidder_found",
+			metricsDisabled: false,
+			adapterName:     openrtb_ext.BidderName(adapter),
+			expectedCount:   1,
+		},
+		{
+			name:            "enabled_bidder_not_found",
+			metricsDisabled: false,
+			adapterName:     fakeBidder,
+			expectedCount:   0,
+		},
+		{
+			name:            "disabled",
+			metricsDisabled: true,
+			adapterName:     openrtb_ext.BidderName(adapter),
+			expectedCount:   0,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			registry := metrics.NewRegistry()
+			m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterBuyerUIDScrubbed: tt.metricsDisabled}, nil, nil)
+
+			m.RecordAdapterBuyerUIDScrubbed(tt.adapterName)
+
+			assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].BuyerUIDScrubbed.Count())
+		})
+	}
+}
+
 func TestRecordAdapterGDPRRequestBlocked(t *testing.T) {
 	var fakeBidder openrtb_ext.BidderName = "fooAdvertising"
 	adapter := "AnyName"
 	lowerCaseAdapterName := "anyname"
 
 	tests := []struct {
-		description     string
+		name            string
 		metricsDisabled bool
 		adapterName     openrtb_ext.BidderName
 		expectedCount   int64
 	}{
 		{
-			description:     "",
+			name:            "enabled_bidder_found",
 			metricsDisabled: false,
 			adapterName:     openrtb_ext.BidderName(adapter),
 			expectedCount:   1,
 		},
 		{
-			description:     "",
+			name:            "enabled_bidder_not_found",
 			metricsDisabled: false,
 			adapterName:     fakeBidder,
 			expectedCount:   0,
 		},
 		{
-			description:     "",
+			name:            "disabled",
 			metricsDisabled: true,
 			adapterName:     openrtb_ext.BidderName(adapter),
 			expectedCount:   0,
 		},
 	}
 	for _, tt := range tests {
-		registry := metrics.NewRegistry()
-		m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil)
+		t.Run(tt.name, func(t *testing.T) {
+			registry := metrics.NewRegistry()
+			m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil)
 
-		m.RecordAdapterGDPRRequestBlocked(tt.adapterName)
+			m.RecordAdapterGDPRRequestBlocked(tt.adapterName)
 
-		assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count(), tt.description)
+			assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count())
+		})
 	}
 }
 
diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go
index a74c8b6c..3ce2f222 100644
--- a/metrics/prometheus/prometheus_test.go
+++ b/metrics/prometheus/prometheus_test.go
@@ -64,7 +64,7 @@ func TestMetricCountGatekeeping(t *testing.T) {
 	// Verify Per-Adapter Cardinality
 	// - This assertion provides a warning for newly added adapter metrics. Threre are 40+ adapters which makes the
 	//   cost of new per-adapter metrics rather expensive. Thought should be given when adding new per-adapter metrics.
-	assert.True(t, perAdapterCardinalityCount <= 30, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount)
+	assert.True(t, perAdapterCardinalityCount <= 31, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount)
 }
 
 func TestConnectionMetrics(t *testing.T) {
@@ -1635,6 +1635,7 @@ func TestDisabledMetrics(t *testing.T) {
 		Namespace: "prebid",
 		Subsystem: "server",
 	}, config.DisabledMetrics{
+		AdapterBuyerUIDScrubbed:   true,
 		AdapterConnectionMetrics:  true,
 		AdapterGDPRRequestBlocked: true,
 	},
@@ -1642,6 +1643,7 @@ func TestDisabledMetrics(t *testing.T) {
 
 	// Assert counter vector was not initialized
 	assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil")
+	assert.Nil(t, prometheusMetrics.adapterScrubbedBuyerUIDs, "Counter Vector adapterScrubbedBuyerUIDs should be nil")
 	assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil")
 	assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil")
 	assert.Nil(t, prometheusMetrics.adapterGDPRBlockedRequests, "Counter Vector adapterGDPRBlockedRequests should be nil")
@@ -1789,6 +1791,45 @@ func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expecte
 	assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum")
 }
 
+func TestRecordAdapterBuyerUIDScrubbed(t *testing.T) {
+
+	tests := []struct {
+		name          string
+		disabled      bool
+		expectedCount float64
+	}{
+		{
+			name:          "enabled",
+			disabled:      false,
+			expectedCount: 1,
+		},
+		{
+			name:          "disabled",
+			disabled:      true,
+			expectedCount: 0,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			m := createMetricsForTesting()
+			m.metricsDisabled.AdapterBuyerUIDScrubbed = tt.disabled
+			adapterName := openrtb_ext.BidderName("AnyName")
+			lowerCasedAdapterName := "anyname"
+			m.RecordAdapterBuyerUIDScrubbed(adapterName)
+
+			assertCounterVecValue(t,
+				"Increment adapter buyeruid scrubbed counter",
+				"adapter_buyeruids_scrubbed",
+				m.adapterScrubbedBuyerUIDs,
+				tt.expectedCount,
+				prometheus.Labels{
+					adapterLabel: lowerCasedAdapterName,
+				})
+		})
+	}
+}
+
 func TestRecordAdapterGDPRRequestBlocked(t *testing.T) {
 	m := createMetricsForTesting()
 	adapterName := openrtb_ext.BidderName("AnyName")