Skip to content

Commit acf8cb9

Browse files
authored
refact(audience-logs): Added and refactored audience and feature variable evaluation logs (#380)
1 parent 16f144d commit acf8cb9

File tree

12 files changed

+163
-104
lines changed

12 files changed

+163
-104
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -693,13 +693,15 @@ <T> T getFeatureVariableValueForType(@Nonnull String featureKey,
693693
featureDecision.variation.getVariableIdToFeatureVariableUsageInstanceMap().get(variable.getId());
694694
if (featureVariableUsageInstance != null) {
695695
variableValue = featureVariableUsageInstance.getValue();
696+
logger.info("Got variable value \"{}\" for variable \"{}\" of feature flag \"{}\".", variableValue, variableKey, featureKey);
696697
} else {
697698
variableValue = variable.getDefaultValue();
699+
logger.info("Value is not defined for variable \"{}\". Returning default value \"{}\".", variableKey, variableValue);
698700
}
699701
} else {
700-
logger.info("Feature \"{}\" for variation \"{}\" was not enabled. " +
701-
"The default value is being returned.",
702-
featureKey, featureDecision.variation.getKey(), variableValue, variableKey
702+
logger.info("Feature \"{}\" is not enabled for user \"{}\". " +
703+
"Returning the default variable value \"{}\".",
704+
featureKey, userId, variableValue
703705
);
704706
}
705707
featureEnabled = featureDecision.variation.getFeatureEnabled();
@@ -822,12 +824,12 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
822824
Variation variation = featureDecision.variation;
823825

824826
if (variation != null) {
825-
if (!variation.getFeatureEnabled()) {
826-
logger.info("Feature \"{}\" for variation \"{}\" was not enabled. " +
827-
"The default value is being returned.", featureKey, featureDecision.variation.getKey());
828-
}
829-
830827
featureEnabled = variation.getFeatureEnabled();
828+
if (featureEnabled) {
829+
logger.info("Feature \"{}\" is enabled for user \"{}\".", featureKey, userId);
830+
} else {
831+
logger.info("Feature \"{}\" is not enabled for user \"{}\".", featureKey, userId);
832+
}
831833
} else {
832834
logger.info("User \"{}\" was not bucketed into any variation for feature flag \"{}\". " +
833835
"The default values are being returned.", userId, featureKey);

core-api/src/main/java/com/optimizely/ab/bucketing/DecisionService.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2017-2019, Optimizely, Inc. and contributors *
2+
* Copyright 2017-2020, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -17,7 +17,6 @@
1717

1818
import com.optimizely.ab.OptimizelyRuntimeException;
1919
import com.optimizely.ab.config.*;
20-
import com.optimizely.ab.config.audience.Audience;
2120
import com.optimizely.ab.error.ErrorHandler;
2221
import com.optimizely.ab.internal.ExperimentUtils;
2322
import com.optimizely.ab.internal.ControlAttribute;
@@ -32,6 +31,9 @@
3231
import javax.annotation.Nonnull;
3332
import javax.annotation.Nullable;
3433

34+
import static com.optimizely.ab.internal.LoggingConstants.LoggingEntityType.EXPERIMENT;
35+
import static com.optimizely.ab.internal.LoggingConstants.LoggingEntityType.RULE;
36+
3537
/**
3638
* Optimizely's decision service that determines which variation of an experiment the user will be allocated to.
3739
*
@@ -133,7 +135,7 @@ public Variation getVariation(@Nonnull Experiment experiment,
133135
userProfile = new UserProfile(userId, new HashMap<String, Decision>());
134136
}
135137

136-
if (ExperimentUtils.isUserInExperiment(projectConfig, experiment, filteredAttributes)) {
138+
if (ExperimentUtils.doesUserMeetAudienceConditions(projectConfig, experiment, filteredAttributes, EXPERIMENT, experiment.getKey())) {
137139
String bucketingId = getBucketingId(userId, filteredAttributes);
138140
variation = bucketer.bucket(experiment, bucketingId, projectConfig);
139141

@@ -221,25 +223,24 @@ FeatureDecision getVariationForFeatureInRollout(@Nonnull FeatureFlag featureFlag
221223
Variation variation;
222224
for (int i = 0; i < rolloutRulesLength - 1; i++) {
223225
Experiment rolloutRule = rollout.getExperiments().get(i);
224-
Audience audience = projectConfig.getAudienceIdMapping().get(rolloutRule.getAudienceIds().get(0));
225-
if (ExperimentUtils.isUserInExperiment(projectConfig, rolloutRule, filteredAttributes)) {
226+
if (ExperimentUtils.doesUserMeetAudienceConditions(projectConfig, rolloutRule, filteredAttributes, RULE, Integer.toString(i + 1))) {
226227
variation = bucketer.bucket(rolloutRule, bucketingId, projectConfig);
227228
if (variation == null) {
228229
break;
229230
}
230231
return new FeatureDecision(rolloutRule, variation,
231232
FeatureDecision.DecisionSource.ROLLOUT);
232233
} else {
233-
logger.debug("User \"{}\" did not meet the conditions to be in rollout rule for audience \"{}\".",
234-
userId, audience.getName());
234+
logger.debug("User \"{}\" does not meet conditions for targeting rule \"{}\".", userId, i + 1);
235235
}
236236
}
237237

238238
// get last rule which is the fall back rule
239239
Experiment finalRule = rollout.getExperiments().get(rolloutRulesLength - 1);
240-
if (ExperimentUtils.isUserInExperiment(projectConfig, finalRule, filteredAttributes)) {
240+
if (ExperimentUtils.doesUserMeetAudienceConditions(projectConfig, finalRule, filteredAttributes, RULE, "Everyone Else")) {
241241
variation = bucketer.bucket(finalRule, bucketingId, projectConfig);
242242
if (variation != null) {
243+
logger.debug("User \"{}\" meets conditions for targeting rule \"Everyone Else\".", userId);
243244
return new FeatureDecision(finalRule, variation,
244245
FeatureDecision.DecisionSource.ROLLOUT);
245246
}
@@ -394,7 +395,6 @@ public boolean setForcedVariation(@Nonnull Experiment experiment,
394395
@Nullable String variationKey) {
395396

396397

397-
398398
Variation variation = null;
399399

400400
// keep in mind that you can pass in a variationKey that is null if you want to

core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
7474
logger.error("Audience {} could not be found.", audienceId);
7575
return null;
7676
}
77-
logger.debug("Starting to evaluate audience {} with conditions: \"{}\"", audience.getName(), audience.getConditions());
77+
logger.debug("Starting to evaluate audience \"{}\" with conditions: {}.", audience.getId(), audience.getConditions());
7878
Boolean result = audience.getConditions().evaluate(config, attributes);
79-
logger.debug("Audience {} evaluated to {}", audience.getName(), result);
79+
logger.debug("Audience \"{}\" evaluated to {}.", audience.getId(), result);
8080
return result;
8181
}
8282

core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2016-2019, Optimizely and contributors
3+
* Copyright 2016-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -82,7 +82,7 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
8282
Object userAttributeValue = attributes.get(name);
8383

8484
if (!"custom_attribute".equals(type)) {
85-
logger.warn("Audience condition \"{}\" has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK", this);
85+
logger.warn("Audience condition \"{}\" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.", this);
8686
return null; // unknown type
8787
}
8888
// check user attribute value is equal
@@ -103,7 +103,7 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
103103
userAttributeValue.getClass().getCanonicalName(),
104104
name);
105105
} else {
106-
logger.warn(
106+
logger.debug(
107107
"Audience condition \"{}\" evaluated to UNKNOWN because a null value was passed for user attribute \"{}\"",
108108
this,
109109
name);

core-api/src/main/java/com/optimizely/ab/config/audience/match/UnexpectedValueTypeException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2019, Optimizely and contributors
3+
* Copyright 2019-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
1818
package com.optimizely.ab.config.audience.match;
1919

2020
public class UnexpectedValueTypeException extends Exception {
21-
private static String message = "has an unexpected value type. You may need to upgrade to a newer release of the Optimizely SDK";
21+
private static String message = "has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK.";
2222

2323
public UnexpectedValueTypeException() {
2424
super(message);

core-api/src/main/java/com/optimizely/ab/config/audience/match/UnknownMatchTypeException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2019, Optimizely and contributors
3+
* Copyright 2019-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
1818
package com.optimizely.ab.config.audience.match;
1919

2020
public class UnknownMatchTypeException extends Exception {
21-
private static String message = "uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK";
21+
private static String message = "uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.";
2222

2323
public UnknownMatchTypeException() {
2424
super(message);

core-api/src/main/java/com/optimizely/ab/internal/ExperimentUtils.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2017-2019, Optimizely and contributors
3+
* Copyright 2017-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -56,32 +56,38 @@ public static boolean isExperimentActive(@Nonnull Experiment experiment) {
5656
/**
5757
* Determines whether a user satisfies audience conditions for the experiment.
5858
*
59-
* @param projectConfig the current projectConfig
60-
* @param experiment the experiment we are evaluating audiences for
61-
* @param attributes the attributes of the user
59+
* @param projectConfig the current projectConfig
60+
* @param experiment the experiment we are evaluating audiences for
61+
* @param attributes the attributes of the user
62+
* @param loggingEntityType It can be either experiment or rule.
63+
* @param loggingKey In case of loggingEntityType is experiment it will be experiment key or else it will be rule number.
6264
* @return whether the user meets the criteria for the experiment
6365
*/
64-
public static boolean isUserInExperiment(@Nonnull ProjectConfig projectConfig,
65-
@Nonnull Experiment experiment,
66-
@Nonnull Map<String, ?> attributes) {
66+
public static boolean doesUserMeetAudienceConditions(@Nonnull ProjectConfig projectConfig,
67+
@Nonnull Experiment experiment,
68+
@Nonnull Map<String, ?> attributes,
69+
@Nonnull String loggingEntityType,
70+
@Nonnull String loggingKey) {
6771
if (experiment.getAudienceConditions() != null) {
68-
Boolean resolveReturn = evaluateAudienceConditions(projectConfig, experiment, attributes);
72+
logger.debug("Evaluating audiences for {} \"{}\": {}.", loggingEntityType, loggingKey, experiment.getAudienceConditions());
73+
Boolean resolveReturn = evaluateAudienceConditions(projectConfig, experiment, attributes, loggingEntityType, loggingKey);
6974
return resolveReturn == null ? false : resolveReturn;
7075
} else {
71-
Boolean resolveReturn = evaluateAudience(projectConfig, experiment, attributes);
76+
Boolean resolveReturn = evaluateAudience(projectConfig, experiment, attributes, loggingEntityType, loggingKey);
7277
return Boolean.TRUE.equals(resolveReturn);
7378
}
7479
}
7580

7681
@Nullable
7782
public static Boolean evaluateAudience(@Nonnull ProjectConfig projectConfig,
7883
@Nonnull Experiment experiment,
79-
@Nonnull Map<String, ?> attributes) {
84+
@Nonnull Map<String, ?> attributes,
85+
@Nonnull String loggingEntityType,
86+
@Nonnull String loggingKey) {
8087
List<String> experimentAudienceIds = experiment.getAudienceIds();
8188

8289
// if there are no audiences, ALL users should be part of the experiment
8390
if (experimentAudienceIds.isEmpty()) {
84-
logger.debug("There is no Audience associated with experiment {}", experiment.getKey());
8591
return true;
8692
}
8793

@@ -93,26 +99,28 @@ public static Boolean evaluateAudience(@Nonnull ProjectConfig projectConfig,
9399

94100
OrCondition implicitOr = new OrCondition(conditions);
95101

96-
logger.debug("Evaluating audiences for experiment \"{}\": \"{}\"", experiment.getKey(), conditions);
102+
logger.debug("Evaluating audiences for {} \"{}\": {}.", loggingEntityType, loggingKey, conditions);
97103

98104
Boolean result = implicitOr.evaluate(projectConfig, attributes);
99105

100-
logger.info("Audiences for experiment {} collectively evaluated to {}", experiment.getKey(), result);
106+
logger.info("Audiences for {} \"{}\" collectively evaluated to {}.", loggingEntityType, loggingKey, result);
101107

102108
return result;
103109
}
104110

105111
@Nullable
106112
public static Boolean evaluateAudienceConditions(@Nonnull ProjectConfig projectConfig,
107113
@Nonnull Experiment experiment,
108-
@Nonnull Map<String, ?> attributes) {
114+
@Nonnull Map<String, ?> attributes,
115+
@Nonnull String loggingEntityType,
116+
@Nonnull String loggingKey) {
109117

110118
Condition conditions = experiment.getAudienceConditions();
111119
if (conditions == null) return null;
112-
logger.debug("Evaluating audiences for experiment \"{}\": \"{}\"", experiment.getKey(), conditions.toString());
120+
113121
try {
114122
Boolean result = conditions.evaluate(projectConfig, attributes);
115-
logger.info("Audiences for experiment {} collectively evaluated to {}", experiment.getKey(), result);
123+
logger.info("Audiences for {} \"{}\" collectively evaluated to {}.", loggingEntityType, loggingKey, result);
116124
return result;
117125
} catch (Exception e) {
118126
logger.error("Condition invalid", e);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.internal;
18+
19+
public class LoggingConstants {
20+
public static class LoggingEntityType {
21+
public static final String EXPERIMENT = "experiment";
22+
public static final String RULE = "rule";
23+
}
24+
}

core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2890,8 +2890,7 @@ public void getFeatureVariableValueReturnsDefaultValueWhenFeatureEnabledIsFalse(
28902890

28912891
logbackVerifier.expectMessage(
28922892
Level.INFO,
2893-
"Feature \"" + validFeatureKey + "\" for variation \"Gred\" was not enabled. " +
2894-
"The default value is being returned."
2893+
"Feature \"multi_variate_feature\" is not enabled for user \"genericUserId\". Returning the default variable value \"H\"."
28952894
);
28962895

28972896
assertEquals(expectedValue, value);
@@ -2918,6 +2917,11 @@ public void getFeatureVariableUserInExperimentFeatureOn() throws Exception {
29182917
testUserId,
29192918
Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)),
29202919
expectedValue);
2920+
2921+
logbackVerifier.expectMessage(
2922+
Level.INFO,
2923+
"Got variable value \"F\" for variable \"first_letter\" of feature flag \"multi_variate_feature\"."
2924+
);
29212925
}
29222926

29232927
/**
@@ -3061,6 +3065,11 @@ public void getFeatureVariableValueReturnsDefaultValueWhenNoVariationUsageIsPres
30613065
);
30623066

30633067
assertEquals(expectedValue, value);
3068+
3069+
logbackVerifier.expectMessage(
3070+
Level.INFO,
3071+
"Value is not defined for variable \"integer_variable\". Returning default value \"7\"."
3072+
);
30643073
}
30653074

30663075
/**

0 commit comments

Comments
 (0)