-
Notifications
You must be signed in to change notification settings - Fork 32
Feat(AudienceEvalLogging): Added logging in Audience evaluation and its unit tests #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Pull Request Test Coverage Report for Build 813
💛 - Coveralls |
Pull Request Test Coverage Report for Build 858
💛 - Coveralls |
loganlinn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't specific to this PR, but is there a reason why we use String.format when we log? The Logger class can format more efficiently using the info(String format, Object... arguments) methods. @mnoman09, I know you were following the existing conventions; this is more pointed at @optimizely/fullstack-devs.
| Match matchType = MatchType.getMatchType(match, value, this).getMatcher(); | ||
| Boolean result = matchType.eval(userAttributeValue); | ||
|
|
||
| if (!matchType.getClass().toString().contains("Null") && result == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
substring matching the class name is a very nasty hack 👎
any logging specific to that class is better off being inside that class, i.e. the eval method of NullMatch in this case
| if (!"custom_attribute".equals(type)) { | ||
| MatchType.logger.error(String.format("condition type not equal to `custom_attribute` %s", type)); | ||
| logger.error(String.format("Audience condition \"%s\" has an unknown condition type: %s", this.toString(), type)); | ||
| logger.error(String.format("condition type not equal to `custom_attribute` %s", type)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
single log line per error is highly preferred -- this is not a sufficient error message by itself. If we want additional information in the message, modify the existing logger.error() call, otherwise you can log "supplemental" information like this with a different log level here.
| //Missing attribute value | ||
| logger.debug(String.format("Audience condition \"%s\" evaluated as UNKNOWN because no value was passed for user attribute \"%s\"", this.toString(), name)); | ||
| } else { | ||
| logger.warn( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not justified as warn level IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@loganlinn, to clarify, are you saying that this message is redundant with the warning that we would have logged in castToValueType?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As of commit 2be5a31, we're no longer logging a warning in castToValueType
| return new MatchType(matchType, new NullMatch()); | ||
| } | ||
|
|
||
| logger.warn(String.format("Audience condition \"%s\" condition value \"%s\" data type is inapplicable", userAttribute.toString(), conditionValue)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not justified as warn level IMO.
this would be more informative if we logged matchType.
also, think we shouldn't be logging conditionValue object -- it is potentially very expensive (depends on it's toString() impl) and has potential to log undesired data (PII).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not justified as
warnlevel IMO.
I dunno, a customer probably won't expect an audience condition to become inactive for this reason, and therefore probably ought to be warned. If it helps, the updated log message suggests that the customer upgrade their SDK.
this would be more informative if we logged
matchType.
It's included (along with type, name, and value) in our logging of the audience condition (the userAttribute variable).
also, think we shouldn't be logging
conditionValueobject -- it is potentially very expensive (depends on it'stoString()impl) and has potential to log undesired data (PII).
Good feedback. We're not logging it any more!
| private Match matcher; | ||
|
|
||
| public static MatchType getMatchType(String matchType, Object conditionValue) { | ||
| public static MatchType getMatchType(String matchType, Object conditionValue, UserAttribute userAttribute) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👎 to adding an argument to the signature just for logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@loganlinn Do you have any suggestion to deal it, where we need to log with userAttributes. I am thinking to add some enum types for NullMatch, when NullMatch is returned, we'll also set NullMatchReasonType in the object. So In two or many more different cases we will check it using ReasonType under NullMatch object.
| try { | ||
| return conditions.evaluate(projectConfig, attributes); | ||
| Boolean result = conditions.evaluate(projectConfig, attributes); | ||
| logger.info(String.format("Audiences for experiment %s collectively evaluated as " + result, experiment.getKey())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not justified as info level IMO.
I would rather leave it up to the user of SDK to log this on the caller side than to put here.
| // check user attribute value is equal | ||
| try { | ||
| return MatchType.getMatchType(match, value).getMatcher().eval(userAttributeValue); | ||
| Match matchType = MatchType.getMatchType(match, value, this.toString()).getMatcher(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please see my comments below for this position.
| private Match matcher; | ||
|
|
||
| public static MatchType getMatchType(String matchType, Object conditionValue) { | ||
| public static MatchType getMatchType(String matchType, Object conditionValue, String audienceCondition) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove arg from here.
# Conflicts: # core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java # core-api/src/main/java/com/optimizely/ab/config/audience/match/AttributeMatch.java # core-api/src/main/java/com/optimizely/ab/internal/ExperimentUtils.java # core-api/src/test/java/com/optimizely/ab/internal/ExperimentUtilsTest.java
nchilada
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late response! I left a bunch of comments; hope they're not too crazy. @msohailhussain @loganlinn I'd be happy to walk through these offline, if that helps
core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/match/MatchTypeError.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java
Outdated
Show resolved
Hide resolved
| return result; | ||
| } catch (NullPointerException np) { | ||
| MatchType.logger.error(String.format("attribute or value null for match %s", match != null ? match : "legacy condition"), np); | ||
| logger.error(String.format("attribute or value null for match %s", match != null ? match : "legacy condition"), np); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This is a somewhat cryptic log message. @loganlinn @thomaszurkan-optimizely maybe we can audit our code to include all necessary null checks and then get rid of this NullPointerException catcher? Tho maybe it's not worthwhile if we don't expect to get here in practice...
| //Missing attribute value | ||
| logger.debug(String.format("Audience condition \"%s\" evaluated as UNKNOWN because no value was passed for user attribute \"%s\"", this.toString(), name)); | ||
| } else { | ||
| logger.warn( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@loganlinn, to clarify, are you saying that this message is redundant with the warning that we would have logged in castToValueType?
core-api/src/main/java/com/optimizely/ab/config/audience/match/AttributeMatch.java
Outdated
Show resolved
Hide resolved
nchilada
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates! Lemme see if I can get another code review from someone who's more familiar with this.
core-api/src/main/java/com/optimizely/ab/config/audience/match/MatchTypeError.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java
Outdated
Show resolved
Hide resolved
nchilada
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates! And thanks for writing unit tests for this, too. It's looking good apart from a couple suggestions!
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/test/java/com/optimizely/ab/internal/ExperimentUtilsTest.java
Outdated
Show resolved
Hide resolved
nchilada
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One remaining request for the test cases!
core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java
Outdated
Show resolved
Hide resolved
| Match matchType = MatchType.getMatchType(match, value).getMatcher(); | ||
| Boolean result = matchType.eval(userAttributeValue); | ||
|
|
||
| if (!(matchType instanceof NullMatch) && result == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm opposed to this approach. The instanceof and numerous logic branches are hard to follow and logging makes assumptions about the implementation details of matcher. This may be correct right now, but not guaranteed in the future. It smells out of being out of place and not a proper utilization of MatchType/Matcher/etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, smells a little funky to me too.
The alternative is logging from the matcher itself. Given the type of log message that we want, that means passing down the entire audience condition to the matcher, which I think you'd been unsure about. And we'd also want to pass an additional boolean if we want to preserve distinct messages for "no value" and "a null value".
How should we proceed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My opinion (which I will also mention in other SDK's PRs) is that it's okay to pass additional data for logging purposes. @msohailhussain @mikeng13 FYI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm so late to the party. But, I agree: It stinks. 2 questions on approach:
- What's wrong with logging the null messages on the match type and remove the if instanceof?
- Why not just create two exceptions and catch for those and log that way? It is way cleaner than this.
core-api/src/main/java/com/optimizely/ab/config/audience/match/MatchTypeError.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java
Outdated
Show resolved
Hide resolved
nchilada
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all the updates! The log messages look good to me, but will wait to hear @loganlinn's feedback on how the mismatch logs are instrumented.
thomaszurkan-optimizely
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change the NullMatchType errors to exceptions we catch with the error string already there?
| Match matchType = MatchType.getMatchType(match, value).getMatcher(); | ||
| Boolean result = matchType.eval(userAttributeValue); | ||
|
|
||
| if (!(matchType instanceof NullMatch) && result == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm so late to the party. But, I agree: It stinks. 2 questions on approach:
- What's wrong with logging the null messages on the match type and remove the if instanceof?
- Why not just create two exceptions and catch for those and log that way? It is way cleaner than this.
| this); | ||
| } | ||
| return result; | ||
| } catch (NullPointerException np) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a couple of new exception type instead of matchType instanceof NullMatch?
| MatchType.logger.error(String.format("attribute or value null for match %s", match != null ? match : "legacy condition"), np); | ||
| logger.error("attribute or value null for match {}", match != null ? match : "legacy condition", np); | ||
| return null; | ||
| } catch (NullMatch nm) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an exception. There should be one per error.
|
|
||
| package com.optimizely.ab.config.audience.match; | ||
|
|
||
| public enum NullMatchTypeErrors { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get rid of this class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create two exceptions UnknownMatchTypeException and UnexpectedValueTypeException.
|
|
||
| protected NullMatch() { | ||
| this.value = null; | ||
| protected NullMatch(NullMatchTypeErrors nullMatchTypeErrors) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get rid of this constructor.
| package com.optimizely.ab.config.audience.match; | ||
|
|
||
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
| public class NullMatch extends Throwable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create two new exceptions with the suffix Exception and set the appropriate errorDescription
thomaszurkan-optimizely
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks!
Summary