Skip to content

Commit bdb4032

Browse files
oakbanialiabbasrizvi
authored andcommitted
feat(Audience Evaluation): Audience Logging (#154)
1 parent cc9dd78 commit bdb4032

10 files changed

+1216
-384
lines changed

Diff for: src/Optimizely/DecisionService/DecisionService.php

+7-7
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public function getVariation(Experiment $experiment, $userId, $attributes = null
145145
}
146146
}
147147

148-
if (!Validator::isUserInExperiment($this->_projectConfig, $experiment, $attributes)) {
148+
if (!Validator::isUserInExperiment($this->_projectConfig, $experiment, $attributes, $this->_logger)) {
149149
$this->_logger->log(
150150
Logger::INFO,
151151
sprintf('User "%s" does not meet conditions to be in experiment "%s".', $userId, $experiment->getKey())
@@ -188,10 +188,10 @@ public function getVariationForFeature(FeatureFlag $featureFlag, $userId, $userA
188188
Logger::INFO,
189189
"User '{$userId}' is bucketed into rollout for feature flag '{$featureFlag->getKey()}'."
190190
);
191-
191+
192192
return $decision;
193193
}
194-
194+
195195
$this->_logger->log(
196196
Logger::INFO,
197197
"User '{$userId}' is not bucketed into rollout for feature flag '{$featureFlag->getKey()}'."
@@ -291,7 +291,7 @@ public function getVariationForFeatureRollout(FeatureFlag $featureFlag, $userId,
291291
$experiment = $rolloutRules[$i];
292292

293293
// Evaluate if user meets the audience condition of this rollout rule
294-
if (!Validator::isUserInExperiment($this->_projectConfig, $experiment, $userAttributes)) {
294+
if (!Validator::isUserInExperiment($this->_projectConfig, $experiment, $userAttributes, $this->_logger)) {
295295
$this->_logger->log(
296296
Logger::DEBUG,
297297
sprintf("User '%s' did not meet the audience conditions to be in rollout rule '%s'.", $userId, $experiment->getKey())
@@ -309,16 +309,16 @@ public function getVariationForFeatureRollout(FeatureFlag $featureFlag, $userId,
309309
}
310310
// Evaluate Everyone Else Rule / Last Rule now
311311
$experiment = $rolloutRules[sizeof($rolloutRules)-1];
312-
312+
313313
// Evaluate if user meets the audience condition of Everyone Else Rule / Last Rule now
314-
if (!Validator::isUserInExperiment($this->_projectConfig, $experiment, $userAttributes)) {
314+
if (!Validator::isUserInExperiment($this->_projectConfig, $experiment, $userAttributes, $this->_logger)) {
315315
$this->_logger->log(
316316
Logger::DEBUG,
317317
sprintf("User '%s' did not meet the audience conditions to be in rollout rule '%s'.", $userId, $experiment->getKey())
318318
);
319319
return null;
320320
}
321-
321+
322322
$variation = $this->_bucketer->bucket($this->_projectConfig, $experiment, $bucketing_id, $userId);
323323
if ($variation && $variation->getKey()) {
324324
return new FeatureDecision($experiment, $variation, FeatureDecision::DECISION_SOURCE_ROLLOUT);

Diff for: src/Optimizely/Enums/AudienceEvaluationLogs.php

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/**
3+
* Copyright 2019, Optimizely
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+
18+
namespace Optimizely\Enums;
19+
20+
class AudienceEvaluationLogs
21+
{
22+
const AUDIENCE_EVALUATION_RESULT = "Audience \"%s\" evaluated to %s.";
23+
const AUDIENCE_EVALUATION_RESULT_COMBINED = "Audiences for experiment \"%s\" collectively evaluated to %s.";
24+
const EVALUATING_AUDIENCES_COMBINED = "Evaluating audiences for experiment \"%s\": %s.";
25+
const EVALUATING_AUDIENCE = "Starting to evaluate audience \"%s\" with conditions: %s.";
26+
const INFINITE_ATTRIBUTE_VALUE = "Audience condition %s evaluated to UNKNOWN because the number value for user attribute \"%s\" is not in the range [-2^53, +2^53].";
27+
const MISSING_ATTRIBUTE_VALUE = "Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute \"%s\".";
28+
const NULL_ATTRIBUTE_VALUE = "Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute \"%s\".";
29+
const UNEXPECTED_TYPE = "Audience condition %s evaluated to UNKNOWN because a value of type \"%s\" was passed for user attribute \"%s\".";
30+
31+
const UNKNOWN_CONDITION_TYPE = "Audience condition %s uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.";
32+
const UNKNOWN_CONDITION_VALUE = "Audience condition %s has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK.";
33+
const UNKNOWN_MATCH_TYPE = "Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.";
34+
}

Diff for: src/Optimizely/Utils/CustomAttributeConditionEvaluator.php

+146-29
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
namespace Optimizely\Utils;
1919

20+
use Monolog\Logger;
21+
use Optimizely\Enums\AudienceEvaluationLogs as logs;
2022
use Optimizely\Utils\Validator;
2123

2224
class CustomAttributeConditionEvaluator
@@ -31,17 +33,19 @@ class CustomAttributeConditionEvaluator
3133

3234
/**
3335
* @var UserAttributes
34-
*/
36+
*/
3537
protected $userAttributes;
3638

3739
/**
3840
* CustomAttributeConditionEvaluator constructor
3941
*
4042
* @param array $userAttributes Associative array of user attributes to values.
43+
* @param $logger LoggerInterface.
4144
*/
42-
public function __construct(array $userAttributes)
45+
public function __construct(array $userAttributes, $logger)
4346
{
4447
$this->userAttributes = $userAttributes;
48+
$this->logger = $logger;
4549
}
4650

4751
/**
@@ -73,7 +77,7 @@ protected function getMatchTypes()
7377
/**
7478
* Gets the evaluator method name for the given match type.
7579
*
76-
* @param string $matchType Match type for which to get evaluator.
80+
* @param string $matchType Match type for which to get evaluator.
7781
*
7882
* @return string Corresponding evaluator method name.
7983
*/
@@ -92,13 +96,13 @@ protected function getEvaluatorByMatchType($matchType)
9296
/**
9397
* Checks if the given input is a valid value for exact condition evaluation.
9498
*
95-
* @param $value Input to check.
99+
* @param $value Input to check.
96100
*
97-
* @return boolean true if given input is a string/boolean/finite number, false otherwise.
101+
* @return boolean true if given input is a string/boolean/number, false otherwise.
98102
*/
99-
protected function isValueValidForExactConditions($value)
103+
protected function isValueTypeValidForExactConditions($value)
100104
{
101-
if (is_string($value) || is_bool($value) || Validator::isFiniteNumber($value)) {
105+
if (is_string($value) || is_bool($value) || is_int($value) || is_float($value)) {
102106
return true;
103107
}
104108

@@ -108,7 +112,7 @@ protected function isValueValidForExactConditions($value)
108112
/**
109113
* Evaluate the given exact match condition for the given user attributes.
110114
*
111-
* @param object $condition
115+
* @param object $condition
112116
*
113117
* @return null|boolean true if the user attribute value is equal (===) to the condition value,
114118
* false if the user attribute value is not equal (!==) to the condition value,
@@ -122,11 +126,34 @@ protected function exactEvaluator($condition)
122126
$conditionValue = $condition['value'];
123127
$userValue = isset($this->userAttributes[$conditionName]) ? $this->userAttributes[$conditionName]: null;
124128

125-
if (!$this->isValueValidForExactConditions($userValue)
126-
|| !$this->isValueValidForExactConditions($conditionValue)
127-
|| !Validator::areValuesSameType($conditionValue, $userValue)
128-
) {
129-
return null;
129+
if (!$this->isValueTypeValidForExactConditions($conditionValue) ||
130+
((is_int($conditionValue) || is_float($conditionValue)) && !Validator::isFiniteNumber($conditionValue))) {
131+
$this->logger->log(Logger::WARNING, sprintf(
132+
logs::UNKNOWN_CONDITION_VALUE,
133+
json_encode($condition)
134+
));
135+
return null;
136+
}
137+
138+
if (!$this->isValueTypeValidForExactConditions($userValue) ||
139+
!Validator::areValuesSameType($conditionValue, $userValue)) {
140+
$this->logger->log(Logger::WARNING, sprintf(
141+
logs::UNEXPECTED_TYPE,
142+
json_encode($condition),
143+
gettype($userValue),
144+
$conditionName
145+
));
146+
return null;
147+
}
148+
149+
if ((is_int($userValue) || is_float($userValue)) &&
150+
!Validator::isFiniteNumber($userValue)) {
151+
$this->logger->log(Logger::WARNING, sprintf(
152+
logs::INFINITE_ATTRIBUTE_VALUE,
153+
json_encode($condition),
154+
$conditionName
155+
));
156+
return null;
130157
}
131158

132159
return $conditionValue == $userValue;
@@ -135,7 +162,7 @@ protected function exactEvaluator($condition)
135162
/**
136163
* Evaluate the given exists match condition for the given user attributes.
137164
*
138-
* @param object $condition
165+
* @param object $condition
139166
*
140167
* @return null|boolean true if both:
141168
* 1) the user attributes have a value for the given condition, and
@@ -151,7 +178,7 @@ protected function existsEvaluator($condition)
151178
/**
152179
* Evaluate the given greater than match condition for the given user attributes.
153180
*
154-
* @param object $condition
181+
* @param object $condition
155182
*
156183
* @return boolean true if the user attribute value is greater than the condition value,
157184
* false if the user attribute value is less than or equal to the condition value,
@@ -164,7 +191,30 @@ protected function greaterThanEvaluator($condition)
164191
$conditionValue = $condition['value'];
165192
$userValue = isset($this->userAttributes[$conditionName]) ? $this->userAttributes[$conditionName]: null;
166193

167-
if (!Validator::isFiniteNumber($userValue) || !Validator::isFiniteNumber($conditionValue)) {
194+
if (!Validator::isFiniteNumber($conditionValue)) {
195+
$this->logger->log(Logger::WARNING, sprintf(
196+
logs::UNKNOWN_CONDITION_VALUE,
197+
json_encode($condition)
198+
));
199+
return null;
200+
}
201+
202+
if (!(is_int($userValue) || is_float($userValue))) {
203+
$this->logger->log(Logger::WARNING, sprintf(
204+
logs::UNEXPECTED_TYPE,
205+
json_encode($condition),
206+
gettype($userValue),
207+
$conditionName
208+
));
209+
return null;
210+
}
211+
212+
if (!Validator::isFiniteNumber($userValue)) {
213+
$this->logger->log(Logger::WARNING, sprintf(
214+
logs::INFINITE_ATTRIBUTE_VALUE,
215+
json_encode($condition),
216+
$conditionName
217+
));
168218
return null;
169219
}
170220

@@ -174,7 +224,7 @@ protected function greaterThanEvaluator($condition)
174224
/**
175225
* Evaluate the given less than match condition for the given user attributes.
176226
*
177-
* @param object $condition
227+
* @param object $condition
178228
*
179229
* @return boolean true if the user attribute value is less than the condition value,
180230
* false if the user attribute value is greater than or equal to the condition value,
@@ -187,30 +237,67 @@ protected function lessThanEvaluator($condition)
187237
$conditionValue = $condition['value'];
188238
$userValue = isset($this->userAttributes[$conditionName]) ? $this->userAttributes[$conditionName]: null;
189239

190-
if (!Validator::isFiniteNumber($userValue) || !Validator::isFiniteNumber($conditionValue)) {
240+
if (!Validator::isFiniteNumber($conditionValue)) {
241+
$this->logger->log(Logger::WARNING, sprintf(
242+
logs::UNKNOWN_CONDITION_VALUE,
243+
json_encode($condition)
244+
));
245+
return null;
246+
}
247+
248+
if (!(is_int($userValue) || is_float($userValue))) {
249+
$this->logger->log(Logger::WARNING, sprintf(
250+
logs::UNEXPECTED_TYPE,
251+
json_encode($condition),
252+
gettype($userValue),
253+
$conditionName
254+
));
255+
return null;
256+
}
257+
258+
if (!Validator::isFiniteNumber($userValue)) {
259+
$this->logger->log(Logger::WARNING, sprintf(
260+
logs::INFINITE_ATTRIBUTE_VALUE,
261+
json_encode($condition),
262+
$conditionName
263+
));
191264
return null;
192265
}
193266

194267
return $userValue < $conditionValue;
195268
}
196269

197270
/**
198-
* Evaluate the given substring than match condition for the given user attributes.
199-
*
200-
* @param object $condition
201-
*
202-
* @return boolean true if the condition value is a substring of the user attribute value,
203-
* false if the condition value is not a substring of the user attribute value,
204-
* null if the condition value isn't a string or the user attribute value
205-
* isn't a string.
206-
*/
271+
* Evaluate the given substring than match condition for the given user attributes.
272+
*
273+
* @param object $condition
274+
*
275+
* @return boolean true if the condition value is a substring of the user attribute value,
276+
* false if the condition value is not a substring of the user attribute value,
277+
* null if the condition value isn't a string or the user attribute value
278+
* isn't a string.
279+
*/
207280
protected function substringEvaluator($condition)
208281
{
209282
$conditionName = $condition['name'];
210283
$conditionValue = $condition['value'];
211284
$userValue = isset($this->userAttributes[$conditionName]) ? $this->userAttributes[$conditionName]: null;
212285

213-
if (!is_string($userValue) || !is_string($conditionValue)) {
286+
if (!is_string($conditionValue)) {
287+
$this->logger->log(Logger::WARNING, sprintf(
288+
logs::UNKNOWN_CONDITION_VALUE,
289+
json_encode($condition)
290+
));
291+
return null;
292+
}
293+
294+
if (!is_string($userValue)) {
295+
$this->logger->log(Logger::WARNING, sprintf(
296+
logs::UNEXPECTED_TYPE,
297+
json_encode($condition),
298+
gettype($userValue),
299+
$conditionName
300+
));
214301
return null;
215302
}
216303

@@ -220,7 +307,7 @@ protected function substringEvaluator($condition)
220307
/**
221308
* Function to evaluate audience conditions against user's attributes.
222309
*
223-
* @param array $leafCondition Condition to be evaluated.
310+
* @param array $leafCondition Condition to be evaluated.
224311
*
225312
* @return null|boolean true/false if the given user attributes match/don't match the given conditions,
226313
* null if the given user attributes and conditions can't be evaluated.
@@ -230,6 +317,10 @@ public function evaluate($leafCondition)
230317
$leafCondition = $this->setNullForMissingKeys($leafCondition);
231318

232319
if ($leafCondition['type'] !== self::CUSTOM_ATTRIBUTE_CONDITION_TYPE) {
320+
$this->logger->log(Logger::WARNING, sprintf(
321+
logs::UNKNOWN_CONDITION_TYPE,
322+
json_encode($leafCondition)
323+
));
233324
return null;
234325
}
235326

@@ -240,9 +331,35 @@ public function evaluate($leafCondition)
240331
}
241332

242333
if (!in_array($conditionMatch, $this->getMatchTypes())) {
334+
$this->logger->log(Logger::WARNING, sprintf(
335+
logs::UNKNOWN_MATCH_TYPE,
336+
json_encode($leafCondition)
337+
));
243338
return null;
244339
}
245340

341+
$conditionName = $leafCondition['name'];
342+
343+
if ($leafCondition['match'] !== self::EXISTS_MATCH_TYPE) {
344+
if (!array_key_exists($conditionName, $this->userAttributes)) {
345+
$this->logger->log(Logger::DEBUG, sprintf(
346+
logs::MISSING_ATTRIBUTE_VALUE,
347+
json_encode($leafCondition),
348+
$conditionName
349+
));
350+
return null;
351+
}
352+
353+
if (!isset($this->userAttributes[$conditionName])) {
354+
$this->logger->log(Logger::DEBUG, sprintf(
355+
logs::NULL_ATTRIBUTE_VALUE,
356+
json_encode($leafCondition),
357+
$conditionName
358+
));
359+
return null;
360+
}
361+
}
362+
246363
$evaluatorForMatch = $this->getEvaluatorByMatchType($conditionMatch);
247364
return $this->$evaluatorForMatch($leafCondition);
248365
}

0 commit comments

Comments
 (0)