Skip to content

Commit 1a55dc4

Browse files
Price Floor Logs Update (#3924)
1 parent bf503d2 commit 1a55dc4

16 files changed

Lines changed: 1183 additions & 398 deletions

src/main/java/org/prebid/server/floors/BasicPriceFloorProcessor.java

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.prebid.server.log.ConditionalLogger;
2222
import org.prebid.server.log.Logger;
2323
import org.prebid.server.log.LoggerFactory;
24+
import org.prebid.server.metric.MetricName;
25+
import org.prebid.server.metric.Metrics;
2426
import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebidFloors;
2527
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
2628
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
@@ -52,17 +54,23 @@ public class BasicPriceFloorProcessor implements PriceFloorProcessor {
5254

5355
private final PriceFloorFetcher floorFetcher;
5456
private final PriceFloorResolver floorResolver;
57+
private final Metrics metrics;
5558
private final JacksonMapper mapper;
59+
private final double logSamplingRate;
5660

5761
private final RandomWeightedEntrySupplier<PriceFloorModelGroup> modelPicker;
5862

5963
public BasicPriceFloorProcessor(PriceFloorFetcher floorFetcher,
6064
PriceFloorResolver floorResolver,
61-
JacksonMapper mapper) {
65+
Metrics metrics,
66+
JacksonMapper mapper,
67+
double logSamplingRate) {
6268

6369
this.floorFetcher = Objects.requireNonNull(floorFetcher);
6470
this.floorResolver = Objects.requireNonNull(floorResolver);
71+
this.metrics = Objects.requireNonNull(metrics);
6572
this.mapper = Objects.requireNonNull(mapper);
73+
this.logSamplingRate = logSamplingRate;
6674

6775
modelPicker = new RandomPositiveWeightedEntrySupplier<>(BasicPriceFloorProcessor::resolveModelGroupWeight);
6876
}
@@ -82,7 +90,7 @@ public BidRequest enrichWithPriceFloors(BidRequest bidRequest,
8290
return disableFloorsForRequest(bidRequest);
8391
}
8492

85-
final PriceFloorRules floors = resolveFloors(account, bidRequest, errors);
93+
final PriceFloorRules floors = resolveFloors(account, bidRequest, warnings);
8694
return updateBidRequestWithFloors(bidRequest, bidder, floors, errors, warnings);
8795
}
8896

@@ -122,49 +130,13 @@ private static PriceFloorRules extractRequestFloors(BidRequest bidRequest) {
122130
return ObjectUtil.getIfNotNull(prebid, ExtRequestPrebid::getFloors);
123131
}
124132

125-
private PriceFloorRules resolveFloors(Account account, BidRequest bidRequest, List<String> errors) {
133+
private PriceFloorRules resolveFloors(Account account, BidRequest bidRequest, List<String> warnings) {
126134
final PriceFloorRules requestFloors = extractRequestFloors(bidRequest);
127135

128136
final FetchResult fetchResult = floorFetcher.fetch(account);
129-
final FetchStatus fetchStatus = ObjectUtil.getIfNotNull(fetchResult, FetchResult::getFetchStatus);
137+
final FetchStatus fetchStatus = fetchResult.getFetchStatus();
130138

131-
if (fetchResult != null && fetchStatus == FetchStatus.success && shouldUseDynamicData(account, fetchResult)) {
132-
final PriceFloorRules mergedFloors = mergeFloors(requestFloors, fetchResult.getRulesData());
133-
return createFloorsFrom(mergedFloors, fetchStatus, PriceFloorLocation.fetch);
134-
}
135-
136-
if (requestFloors != null) {
137-
try {
138-
final Optional<AccountPriceFloorsConfig> priceFloorsConfig = Optional.of(account)
139-
.map(Account::getAuction)
140-
.map(AccountAuctionConfig::getPriceFloors);
141-
142-
final Long maxRules = priceFloorsConfig.map(AccountPriceFloorsConfig::getMaxRules)
143-
.orElse(null);
144-
final Long maxDimensions = priceFloorsConfig.map(AccountPriceFloorsConfig::getMaxSchemaDims)
145-
.orElse(null);
146-
147-
PriceFloorRulesValidator.validateRules(
148-
requestFloors,
149-
PriceFloorsConfigResolver.resolveMaxValue(maxRules),
150-
PriceFloorsConfigResolver.resolveMaxValue(maxDimensions));
151-
152-
return createFloorsFrom(requestFloors, fetchStatus, PriceFloorLocation.request);
153-
} catch (PreBidException e) {
154-
errors.add("Failed to parse price floors from request, with a reason: %s".formatted(e.getMessage()));
155-
conditionalLogger.error(
156-
"Failed to parse price floors from request with id: '%s', with a reason: %s"
157-
.formatted(bidRequest.getId(), e.getMessage()),
158-
0.01d);
159-
}
160-
}
161-
162-
return createFloorsFrom(null, fetchStatus, PriceFloorLocation.noData);
163-
}
164-
165-
private static boolean shouldUseDynamicData(Account account, FetchResult fetchResult) {
166-
final boolean isUsingDynamicDataAllowed = Optional.of(account)
167-
.map(Account::getAuction)
139+
final boolean isUsingDynamicDataAllowed = Optional.ofNullable(account.getAuction())
168140
.map(AccountAuctionConfig::getPriceFloors)
169141
.map(AccountPriceFloorsConfig::getUseDynamicData)
170142
.map(BooleanUtils::isNotFalse)
@@ -175,12 +147,73 @@ private static boolean shouldUseDynamicData(Account account, FetchResult fetchRe
175147
.map(rate -> ThreadLocalRandom.current().nextInt(USE_FETCH_DATA_RATE_MAX) < rate)
176148
.orElse(true);
177149

178-
return isUsingDynamicDataAllowed && shouldUseDynamicData;
150+
if (fetchStatus == FetchStatus.success && isUsingDynamicDataAllowed && shouldUseDynamicData) {
151+
final PriceFloorRules mergedFloors = mergeFloors(requestFloors, fetchResult.getRulesData());
152+
return createFloorsFrom(mergedFloors, fetchStatus, PriceFloorLocation.fetch);
153+
}
154+
155+
final String fetchErrorMessage = resolveFetchErrorMessage(fetchResult, isUsingDynamicDataAllowed);
156+
return requestFloors == null
157+
? noPriceFloorData(fetchStatus, account.getId(), bidRequest.getId(), fetchErrorMessage, warnings)
158+
: getPriceFloorRules(bidRequest, account, requestFloors, fetchStatus, fetchErrorMessage, warnings);
159+
}
160+
161+
private static String resolveFetchErrorMessage(FetchResult fetchResult, boolean isUsingDynamicDataAllowed) {
162+
return switch (fetchResult.getFetchStatus()) {
163+
case inprogress -> null;
164+
case error, timeout, none -> fetchResult.getErrorMessage();
165+
case success -> isUsingDynamicDataAllowed ? null : "Using dynamic data is not allowed";
166+
};
167+
}
168+
169+
private PriceFloorRules noPriceFloorData(FetchStatus fetchStatus,
170+
String accountId,
171+
String requestId,
172+
String errorMessage,
173+
List<String> warnings) {
174+
175+
if (errorMessage != null) {
176+
warnings.add(errorMessage);
177+
conditionalLogger.error("No price floor data for account %s and request %s, reason: %s"
178+
.formatted(accountId, requestId, errorMessage), logSamplingRate);
179+
metrics.updateAlertsMetrics(MetricName.general);
180+
}
181+
182+
return createFloorsFrom(null, fetchStatus, PriceFloorLocation.noData);
179183
}
180184

181-
private PriceFloorRules mergeFloors(PriceFloorRules requestFloors,
182-
PriceFloorData providerRulesData) {
185+
private PriceFloorRules getPriceFloorRules(BidRequest bidRequest,
186+
Account account,
187+
PriceFloorRules requestFloors,
188+
FetchStatus fetchStatus,
189+
String fetchErrorMessage,
190+
List<String> warnings) {
191+
192+
try {
193+
final Optional<AccountPriceFloorsConfig> priceFloorsConfig = Optional.of(account.getAuction())
194+
.map(AccountAuctionConfig::getPriceFloors);
195+
196+
final Long maxRules = priceFloorsConfig.map(AccountPriceFloorsConfig::getMaxRules)
197+
.orElse(null);
198+
final Long maxDimensions = priceFloorsConfig.map(AccountPriceFloorsConfig::getMaxSchemaDims)
199+
.orElse(null);
200+
201+
PriceFloorRulesValidator.validateRules(
202+
requestFloors,
203+
PriceFloorsConfigResolver.resolveMaxValue(maxRules),
204+
PriceFloorsConfigResolver.resolveMaxValue(maxDimensions));
205+
206+
return createFloorsFrom(requestFloors, fetchStatus, PriceFloorLocation.request);
207+
} catch (PreBidException e) {
208+
final String errorMessage = fetchErrorMessage == null
209+
? null
210+
: "%s. Following parsing of request price floors is failed: %s"
211+
.formatted(fetchErrorMessage, e.getMessage());
212+
return noPriceFloorData(fetchStatus, account.getId(), bidRequest.getId(), errorMessage, warnings);
213+
}
214+
}
183215

216+
private PriceFloorRules mergeFloors(PriceFloorRules requestFloors, PriceFloorData providerRulesData) {
184217
final Price floorMinPrice = resolveFloorMinPrice(requestFloors);
185218

186219
return (requestFloors != null ? requestFloors.toBuilder() : PriceFloorRules.builder())

src/main/java/org/prebid/server/floors/PriceFloorFetcher.java

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ public FetchResult fetch(Account account) {
9090
final AccountFetchContext accountFetchContext = fetchedData.get(account.getId());
9191

9292
return accountFetchContext != null
93-
? FetchResult.of(accountFetchContext.getRulesData(), accountFetchContext.getFetchStatus())
93+
? FetchResult.of(
94+
accountFetchContext.getRulesData(),
95+
accountFetchContext.getFetchStatus(),
96+
accountFetchContext.getErrorMessage())
9497
: fetchPriceFloorData(account);
9598
}
9699

@@ -99,20 +102,19 @@ private FetchResult fetchPriceFloorData(Account account) {
99102
final Boolean fetchEnabled = ObjectUtil.getIfNotNull(fetchConfig, AccountPriceFloorsFetchConfig::getEnabled);
100103

101104
if (BooleanUtils.isFalse(fetchEnabled)) {
102-
return FetchResult.of(null, FetchStatus.none);
105+
return FetchResult.none("Fetching is disabled");
103106
}
104107

105108
final String accountId = account.getId();
106109
final String fetchUrl = ObjectUtil.getIfNotNull(fetchConfig, AccountPriceFloorsFetchConfig::getUrl);
107110
if (!isUrlValid(fetchUrl)) {
108-
logger.error("Malformed fetch.url: '%s', passed for account %s".formatted(fetchUrl, accountId));
109-
return FetchResult.of(null, FetchStatus.error);
111+
return FetchResult.error("Malformed fetch.url '%s' passed".formatted(fetchUrl));
110112
}
111113
if (!fetchInProgress.contains(accountId)) {
112114
fetchPriceFloorDataAsynchronous(fetchConfig, accountId);
113115
}
114116

115-
return FetchResult.of(null, FetchStatus.inprogress);
117+
return FetchResult.inProgress();
116118
}
117119

118120
private boolean isUrlValid(String url) {
@@ -148,8 +150,8 @@ private void fetchPriceFloorDataAsynchronous(AccountPriceFloorsFetchConfig fetch
148150

149151
fetchInProgress.add(accountId);
150152
httpClient.get(fetchUrl, timeout, resolveMaxFileSize(maxFetchFileSizeKb))
151-
.map(httpClientResponse -> parseFloorResponse(httpClientResponse, fetchConfig, accountId))
152-
.recover(throwable -> recoverFromFailedFetching(throwable, fetchUrl, accountId))
153+
.map(httpClientResponse -> parseFloorResponse(httpClientResponse, fetchConfig))
154+
.recover(throwable -> recoverFromFailedFetching(throwable, fetchUrl))
153155
.map(cacheInfo -> updateCache(cacheInfo, fetchConfig, accountId))
154156
.map(priceFloorData -> createPeriodicTimerForRulesFetch(priceFloorData, fetchConfig, accountId));
155157
}
@@ -159,40 +161,38 @@ private static long resolveMaxFileSize(Long maxSizeInKBytes) {
159161
}
160162

161163
private ResponseCacheInfo parseFloorResponse(HttpClientResponse httpClientResponse,
162-
AccountPriceFloorsFetchConfig fetchConfig,
163-
String accountId) {
164+
AccountPriceFloorsFetchConfig fetchConfig) {
164165

165166
final int statusCode = httpClientResponse.getStatusCode();
166167
if (statusCode != HttpStatus.SC_OK) {
167-
throw new PreBidException("Failed to request for account %s, provider respond with status %s"
168-
.formatted(accountId, statusCode));
168+
throw new PreBidException("Failed to request, provider respond with status %s".formatted(statusCode));
169169
}
170170
final String body = httpClientResponse.getBody();
171171

172172
if (StringUtils.isBlank(body)) {
173-
throw new PreBidException(
174-
"Failed to parse price floor response for account %s, response body can not be empty"
175-
.formatted(accountId));
173+
throw new PreBidException("Failed to parse price floor response, response body can not be empty");
176174
}
177175

178-
final PriceFloorData priceFloorData = parsePriceFloorData(body, accountId);
176+
final PriceFloorData priceFloorData = parsePriceFloorData(body);
177+
179178
PriceFloorRulesValidator.validateRulesData(
180179
priceFloorData,
181180
PriceFloorsConfigResolver.resolveMaxValue(fetchConfig.getMaxRules()),
182181
PriceFloorsConfigResolver.resolveMaxValue(fetchConfig.getMaxSchemaDims()));
183182

184183
return ResponseCacheInfo.of(priceFloorData,
185184
FetchStatus.success,
185+
null,
186186
cacheTtlFromResponse(httpClientResponse, fetchConfig.getUrl()));
187187
}
188188

189-
private PriceFloorData parsePriceFloorData(String body, String accountId) {
189+
private PriceFloorData parsePriceFloorData(String body) {
190190
final PriceFloorData priceFloorData;
191191
try {
192192
priceFloorData = mapper.decodeValue(body, PriceFloorData.class);
193193
} catch (DecodeException e) {
194-
throw new PreBidException("Failed to parse price floor response for account %s, cause: %s"
195-
.formatted(accountId, ExceptionUtils.getMessage(e)));
194+
throw new PreBidException(
195+
"Failed to parse price floor response, cause: %s".formatted(ExceptionUtils.getMessage(e)));
196196
}
197197
return priceFloorData;
198198
}
@@ -220,8 +220,11 @@ private PriceFloorData updateCache(ResponseCacheInfo cacheInfo,
220220
String accountId) {
221221

222222
final long maxAgeTimerId = createMaxAgeTimer(accountId, resolveCacheTtl(cacheInfo, fetchConfig));
223-
final AccountFetchContext fetchContext =
224-
AccountFetchContext.of(cacheInfo.getRulesData(), cacheInfo.getFetchStatus(), maxAgeTimerId);
223+
final AccountFetchContext fetchContext = AccountFetchContext.of(
224+
cacheInfo.getRulesData(),
225+
cacheInfo.getFetchStatus(),
226+
cacheInfo.getErrorMessage(),
227+
maxAgeTimerId);
225228

226229
if (cacheInfo.getFetchStatus() == FetchStatus.success || !fetchedData.containsKey(accountId)) {
227230
fetchedData.put(accountId, fetchContext);
@@ -267,30 +270,27 @@ private Long createMaxAgeTimer(String accountId, long cacheTtl) {
267270
return vertx.setTimer(TimeUnit.SECONDS.toMillis(effectiveCacheTtl), id -> fetchedData.remove(accountId));
268271
}
269272

270-
private Future<ResponseCacheInfo> recoverFromFailedFetching(Throwable throwable,
271-
String fetchUrl,
272-
String accountId) {
273-
273+
private Future<ResponseCacheInfo> recoverFromFailedFetching(Throwable throwable, String fetchUrl) {
274274
metrics.updatePriceFloorFetchMetric(MetricName.failure);
275275

276276
final FetchStatus fetchStatus;
277+
final String errorMessage;
277278
if (throwable instanceof TimeoutException || throwable instanceof ConnectTimeoutException) {
278279
fetchStatus = FetchStatus.timeout;
279-
logger.error("Fetch price floor request timeout for fetch.url: '%s', account %s exceeded."
280-
.formatted(fetchUrl, accountId));
280+
errorMessage = "Fetch price floor request timeout for fetch.url '%s' exceeded.".formatted(fetchUrl);
281281
} else {
282282
fetchStatus = FetchStatus.error;
283-
logger.error(
284-
"Failed to fetch price floor from provider for fetch.url: '%s', account = %s with a reason : %s "
285-
.formatted(fetchUrl, accountId, throwable.getMessage()));
283+
errorMessage = "Failed to fetch price floor from provider for fetch.url '%s', with a reason: %s"
284+
.formatted(fetchUrl, throwable.getMessage());
286285
}
287286

288-
return Future.succeededFuture(ResponseCacheInfo.withStatus(fetchStatus));
287+
return Future.succeededFuture(ResponseCacheInfo.withError(fetchStatus, errorMessage));
289288
}
290289

291290
private PriceFloorData createPeriodicTimerForRulesFetch(PriceFloorData priceFloorData,
292291
AccountPriceFloorsFetchConfig fetchConfig,
293292
String accountId) {
293+
294294
final long accountPeriodicTimeSec =
295295
ObjectUtil.getIfNotNull(fetchConfig, AccountPriceFloorsFetchConfig::getPeriodSec);
296296
final long periodicTimeSec =
@@ -318,6 +318,8 @@ private static class AccountFetchContext {
318318

319319
FetchStatus fetchStatus;
320320

321+
String errorMessage;
322+
321323
Long maxAgeTimerId;
322324
}
323325

@@ -328,10 +330,12 @@ private static class ResponseCacheInfo {
328330

329331
FetchStatus fetchStatus;
330332

333+
String errorMessage;
334+
331335
Long cacheTtl;
332336

333-
public static ResponseCacheInfo withStatus(FetchStatus status) {
334-
return ResponseCacheInfo.of(null, status, null);
337+
public static ResponseCacheInfo withError(FetchStatus status, String errorMessage) {
338+
return ResponseCacheInfo.of(null, status, errorMessage, null);
335339
}
336340
}
337341
}

src/main/java/org/prebid/server/floors/proto/FetchResult.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,18 @@ public class FetchResult {
99
PriceFloorData rulesData;
1010

1111
FetchStatus fetchStatus;
12+
13+
String errorMessage;
14+
15+
public static FetchResult none(String errorMessage) {
16+
return FetchResult.of(null, FetchStatus.none, errorMessage);
17+
}
18+
19+
public static FetchResult error(String errorMessage) {
20+
return FetchResult.of(null, FetchStatus.error, errorMessage);
21+
}
22+
23+
public static FetchResult inProgress() {
24+
return FetchResult.of(null, FetchStatus.inprogress, null);
25+
}
1226
}

src/main/java/org/prebid/server/spring/config/PriceFloorsConfiguration.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.prebid.server.metric.Metrics;
2121
import org.prebid.server.settings.ApplicationSettings;
2222
import org.prebid.server.vertx.httpclient.HttpClient;
23+
import org.springframework.beans.factory.annotation.Value;
2324
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2425
import org.springframework.boot.context.properties.ConfigurationProperties;
2526
import org.springframework.context.annotation.Bean;
@@ -84,9 +85,11 @@ PriceFloorResolver noOpPriceFloorResolver() {
8485
@ConditionalOnProperty(prefix = "price-floors", name = "enabled", havingValue = "true")
8586
PriceFloorProcessor basicPriceFloorProcessor(PriceFloorFetcher floorFetcher,
8687
PriceFloorResolver floorResolver,
87-
JacksonMapper mapper) {
88+
Metrics metrics,
89+
JacksonMapper mapper,
90+
@Value("${logging.sampling-rate:0.01}") double logSamplingRate) {
8891

89-
return new BasicPriceFloorProcessor(floorFetcher, floorResolver, mapper);
92+
return new BasicPriceFloorProcessor(floorFetcher, floorResolver, metrics, mapper, logSamplingRate);
9093
}
9194

9295
@Bean

0 commit comments

Comments
 (0)