Skip to content

Commit a7c8837

Browse files
Populate log and error messages (#6)
1 parent 212c0d7 commit a7c8837

File tree

10 files changed

+648
-65
lines changed

10 files changed

+648
-65
lines changed

src/Optimizely/Bucketer.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
* limitations under the License.
1616
*/
1717
namespace Optimizely;
18+
use Monolog\Logger;
1819
use Optimizely\Entity\Experiment;
1920
use Optimizely\Entity\Variation;
21+
use Optimizely\Logger\LoggerInterface;
2022

2123
/**
2224
* Class Bucketer
@@ -45,6 +47,20 @@ class Bucketer
4547
*/
4648
private static $MAX_HASH_VALUE = 0x100000000;
4749

50+
/**
51+
* @var LoggerInterface Logger for logging messages.
52+
*/
53+
private $_logger;
54+
55+
/**
56+
* Bucketer constructor.
57+
* @param LoggerInterface $logger
58+
*/
59+
public function __construct(LoggerInterface $logger)
60+
{
61+
$this->_logger = $logger;
62+
}
63+
4864
/**
4965
* Generate a hash value to be used in determining which variation the user will be put in.
5066
*
@@ -82,8 +98,8 @@ private function findBucket($userId, $parentId, $trafficAllocations)
8298
{
8399
// Generate bucketing ID based on combination of user ID and experiment ID or group ID.
84100
$bucketingId = $userId.$parentId;
85-
86101
$bucketingNumber = $this->generateBucketValue($bucketingId);
102+
$this->_logger->log(Logger::DEBUG, sprintf('Assigned bucket %s to user "%s".', $bucketingNumber, $userId));
87103

88104
forEach ($trafficAllocations as $trafficAllocation)
89105
{
@@ -116,6 +132,12 @@ public function bucket(ProjectConfig $config, Experiment $experiment, $userId)
116132
if (!is_null($forcedVariations) && isset($forcedVariations[$userId])) {
117133
$variationKey = $forcedVariations[$userId];
118134
$variation = $config->getVariationFromKey($experiment->getKey(), $variationKey);
135+
if ($variationKey) {
136+
$this->_logger->log(
137+
Logger::INFO,
138+
sprintf('User "%s" is forced in variation "%s".', $userId, $variationKey)
139+
);
140+
}
119141
return $variation;
120142
}
121143

@@ -129,21 +151,37 @@ public function bucket(ProjectConfig $config, Experiment $experiment, $userId)
129151

130152
$userExperimentId = $this->findBucket($userId, $group->getId(), $group->getTrafficAllocation());
131153
if (is_null($userExperimentId)) {
154+
$this->_logger->log(Logger::INFO, sprintf('User "%s" is in no experiment.', $userId));
132155
return new Variation();
133156
}
134157

135158
if ($userExperimentId != $experiment->getId()) {
159+
$this->_logger->log(
160+
Logger::INFO,
161+
sprintf('User "%s" is not in experiment %s of group %s.',
162+
$userId, $experiment->getKey(), $experiment->getGroupId()
163+
));
136164
return new Variation();
137165
}
166+
167+
$this->_logger->log(Logger::INFO,
168+
sprintf('User "%s" is in experiment %s of group %s.',
169+
$userId, $experiment->getKey(), $experiment->getGroupId()
170+
));
138171
}
139172

140173
// Bucket user if not in whitelist and in group (if any).
141174
$variationId = $this->findBucket($userId, $experiment->getId(), $experiment->getTrafficAllocation());
142175
if (!is_null($variationId)) {
143176
$variation = $config->getVariationFromId($experiment->getKey(), $variationId);
177+
$this->_logger->log(Logger::INFO,
178+
sprintf('User "%s" is in variation %s of experiment %s.',
179+
$userId, $variation->getKey(), $experiment->getKey()
180+
));
144181
return $variation;
145182
}
146183

184+
$this->_logger->log(Logger::INFO, sprintf('User "%s" is in no variation.', $userId));
147185
return new Variation();
148186
}
149187
}

src/Optimizely/Event/Builder/EventBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ class EventBuilder
3838
/**
3939
* @var string URL to send impression event to.
4040
*/
41-
private static $IMPRESSION_ENDPOINT = 'https://p13nlog.dz.optimizely.com/log/decision';
41+
private static $IMPRESSION_ENDPOINT = 'https://logx.optimizely.com/log/decision';
4242

4343
/**
4444
* @var string URL to send conversion event to.
4545
*/
46-
private static $CONVERSION_ENDPOINT = 'https://p13nlog.dz.optimizely.com/log/event';
46+
private static $CONVERSION_ENDPOINT = 'https://logx.optimizely.com/log/event';
4747

4848
/**
4949
* @var string HTTP method to be used when making call to log endpoint.

src/Optimizely/Optimizely.php

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616
*/
1717
namespace Optimizely;
1818

19+
use Doctrine\Instantiator\Exception\InvalidArgumentException;
1920
use Exception;
20-
use Optimizely\Entity\Experiment;
21+
use Optimizely\Exceptions\InvalidAttributeException;
2122
use Throwable;
23+
use Monolog\Logger;
24+
use Optimizely\Entity\Experiment;
25+
use Optimizely\Logger\DefaultLogger;
2226
use Optimizely\ErrorHandler\ErrorHandlerInterface;
2327
use Optimizely\ErrorHandler\NoOpErrorHandler;
2428
use Optimizely\Event\Builder\EventBuilder;
@@ -65,6 +69,11 @@ class Optimizely
6569
*/
6670
private $_eventBuilder;
6771

72+
/**
73+
* @var boolean Denotes whether Optimizely object is valid or not.
74+
*/
75+
private $_isValid;
76+
6877
/**
6978
* Optimizely constructor for managing Full Stack PHP projects.
7079
*
@@ -80,17 +89,35 @@ public function __construct($datafile,
8089
ErrorHandlerInterface $errorHandler = null,
8190
$skipJsonValidation = false)
8291
{
92+
$this->_isValid = true;
8393
$this->_eventDispatcher = $eventDispatcher ?: new DefaultEventDispatcher();
84-
$this->_logger = $logger ?: new NoOpLogger();;
85-
$this->_errorHandler = $errorHandler ?: new NoOpErrorHandler();;
94+
$this->_logger = $logger ?: new NoOpLogger();
95+
$this->_errorHandler = $errorHandler ?: new NoOpErrorHandler();
96+
97+
if (!$this->validateInputs($datafile, $skipJsonValidation)) {
98+
$this->_isValid = false;
99+
$this->_logger = new DefaultLogger();
100+
$this->_logger->log(Logger::ERROR, 'Provided "datafile" has invalid schema.');
101+
return;
102+
}
86103

87-
$this->validateInputs($datafile, $skipJsonValidation);
88104
try {
89-
$this->_config = new ProjectConfig($datafile);
105+
$this->_config = new ProjectConfig($datafile, $this->_logger, $this->_errorHandler);
90106
}
91-
catch (Throwable $exception) {}
92-
catch (Exception $exception) {}
93-
$this->_bucketer = new Bucketer();
107+
catch (Throwable $exception) {
108+
$this->_isValid = false;
109+
$this->_logger = new DefaultLogger();
110+
$this->_logger->log(Logger::ERROR, 'Provided "datafile" is in an invalid format.');
111+
return;
112+
}
113+
catch (Exception $exception) {
114+
$this->_isValid = false;
115+
$this->_logger = new DefaultLogger();
116+
$this->_logger->log(Logger::ERROR, 'Provided "datafile" is in an invalid format.');
117+
return;
118+
}
119+
120+
$this->_bucketer = new Bucketer($this->_logger);
94121
$this->_eventBuilder = new EventBuilder($this->_bucketer);
95122
}
96123

@@ -119,9 +146,16 @@ private function validateInputs($datafile, $skipJsonValidation)
119146
*/
120147
private function validatePreconditions($experiment, $userId, $attributes)
121148
{
122-
//@TODO(ali): Insert attributes validation
149+
if (!is_null($attributes) && !Validator::areAttributesValid($attributes)) {
150+
$this->_logger->log(Logger::ERROR, 'Provided attributes are in an invalid format.');
151+
$this->_errorHandler->handleError(
152+
new InvalidAttributeException('Provided attributes are in an invalid format.')
153+
);
154+
return false;
155+
}
123156

124157
if (!$experiment->isExperimentRunning()) {
158+
$this->_logger->log(Logger::INFO, sprintf('Experiment "%s" is not running.', $experiment->getKey()));
125159
return false;
126160
}
127161

@@ -130,6 +164,10 @@ private function validatePreconditions($experiment, $userId, $attributes)
130164
}
131165

132166
if (!Validator::isUserInExperiment($this->_config, $experiment, $attributes)) {
167+
$this->_logger->log(
168+
Logger::INFO,
169+
sprintf('User "%s" does not meet conditions to be in experiment "%s".', $userId, $experiment->getKey())
170+
);
133171
return false;
134172
}
135173

@@ -147,27 +185,52 @@ private function validatePreconditions($experiment, $userId, $attributes)
147185
*/
148186
public function activate($experimentKey, $userId, $attributes = null)
149187
{
188+
if (!$this->_isValid) {
189+
$this->_logger->log(Logger::ERROR, 'Datafile has invalid format. Failing "activate".');
190+
return null;
191+
}
192+
150193
$experiment = $this->_config->getExperimentFromKey($experimentKey);
151194

152195
if (is_null($experiment->getKey())) {
196+
$this->_logger->log(Logger::INFO, sprintf('Not activating user "%s".', $userId));
153197
return null;
154198
}
155199

156200
if (!$this->validatePreconditions($experiment, $userId, $attributes)) {
201+
$this->_logger->log(Logger::INFO, sprintf('Not activating user "%s".', $userId));
157202
return null;
158203
}
159204

160205
$variation = $this->_bucketer->bucket($this->_config, $experiment, $userId);
161206
$variationKey = $variation->getKey();
162207

163208
if (is_null($variationKey)) {
209+
$this->_logger->log(Logger::INFO, sprintf('Not activating user "%s".', $userId));
164210
return $variationKey;
165211
}
166212

167213
$impressionEvent = $this->_eventBuilder
168214
->createImpressionEvent($this->_config, $experiment, $variation->getId(), $userId, $attributes);
215+
$this->_logger->log(Logger::INFO, sprintf('Activating user "%s" in experiment "%s".', $userId, $experimentKey));
216+
$this->_logger->log(
217+
Logger::DEBUG,
218+
sprintf('Dispatching impression event to URL %s with params %s.',
219+
$impressionEvent->getUrl(), implode(',', $impressionEvent->getParams())
220+
)
221+
);
169222

170-
$this->_eventDispatcher->dispatchEvent($impressionEvent);
223+
try {
224+
$this->_eventDispatcher->dispatchEvent($impressionEvent);
225+
}
226+
catch (Throwable $exception) {
227+
$this->_logger->log(Logger::ERROR, sprintf(
228+
'Unable to dispatch impression event. Error %s', $exception->getMessage()));
229+
}
230+
catch (Exception $exception) {
231+
$this->_logger->log(Logger::ERROR, sprintf(
232+
'Unable to dispatch impression event. Error %s', $exception->getMessage()));
233+
}
171234

172235
return $variationKey;
173236
}
@@ -182,13 +245,23 @@ public function activate($experimentKey, $userId, $attributes = null)
182245
*/
183246
public function track($eventKey, $userId, $attributes = null, $eventValue = null)
184247
{
248+
if (!$this->_isValid) {
249+
$this->_logger->log(Logger::ERROR, 'Datafile has invalid format. Failing "track".');
250+
return;
251+
}
252+
185253
if (!is_null($attributes) && !Validator::areAttributesValid($attributes)) {
254+
$this->_logger->log(Logger::ERROR, 'Provided attributes are in an invalid format.');
255+
$this->_errorHandler->handleError(
256+
new InvalidAttributeException('Provided attributes are in an invalid format.')
257+
);
186258
return;
187259
}
188260

189261
$event = $this->_config->getEvent($eventKey);
190262

191263
if (is_null($event->getKey())) {
264+
$this->_logger->log(Logger::ERROR, sprintf('Not tracking user "%s" for event "%s".', $userId, $eventKey));
192265
return;
193266
}
194267

@@ -198,6 +271,9 @@ public function track($eventKey, $userId, $attributes = null, $eventValue = null
198271
$experiment = $this->_config->getExperimentFromId($experimentId);
199272
if ($this->validatePreconditions($experiment, $userId, $attributes)) {
200273
array_push($validExperiments, $experiment);
274+
} else {
275+
$this->_logger->log(Logger::INFO, sprintf('Not tracking user "%s" for experiment "%s".',
276+
$userId, $experiment->getKey()));
201277
}
202278
}
203279

@@ -211,7 +287,30 @@ public function track($eventKey, $userId, $attributes = null, $eventValue = null
211287
$attributes,
212288
$eventValue
213289
);
214-
$this->_eventDispatcher->dispatchEvent($conversionEvent);
290+
$this->_logger->log(Logger::INFO, sprintf('Tracking event "%s" for user "%s".', $eventKey, $userId));
291+
$this->_logger->log(
292+
Logger::DEBUG,
293+
sprintf('Dispatching conversion event to URL %s with params %s.',
294+
$conversionEvent->getUrl(), implode(',', $conversionEvent->getParams())
295+
));
296+
297+
try {
298+
$this->_eventDispatcher->dispatchEvent($conversionEvent);
299+
}
300+
catch (Throwable $exception) {
301+
$this->_logger->log(Logger::ERROR, sprintf(
302+
'Unable to dispatch conversion event. Error %s', $exception->getMessage()));
303+
}
304+
catch (Exception $exception) {
305+
$this->_logger->log(Logger::ERROR, sprintf(
306+
'Unable to dispatch conversion event. Error %s', $exception->getMessage()));
307+
}
308+
309+
} else {
310+
$this->_logger->log(
311+
Logger::INFO,
312+
sprintf('There are no valid experiments for event "%s" to track.', $eventKey)
313+
);
215314
}
216315
}
217316

@@ -226,6 +325,11 @@ public function track($eventKey, $userId, $attributes = null, $eventValue = null
226325
*/
227326
public function getVariation($experimentKey, $userId, $attributes = null)
228327
{
328+
if (!$this->_isValid) {
329+
$this->_logger->log(Logger::ERROR, 'Datafile has invalid format. Failing "getVariation".');
330+
return null;
331+
}
332+
229333
$experiment = $this->_config->getExperimentFromKey($experimentKey);
230334

231335
if (is_null($experiment->getKey())) {

0 commit comments

Comments
 (0)