-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#23 pluggable logging with basic impl
- Loading branch information
Showing
11 changed files
with
431 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
api/src/main/java/com/glassdoor/planout4j/logging/AbstractJSONLogger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.glassdoor.planout4j.logging; | ||
|
||
public abstract class AbstractJSONLogger extends AbstractSerializingLogger<String> { | ||
|
||
protected AbstractJSONLogger() { | ||
this(false); | ||
} | ||
|
||
protected AbstractJSONLogger(final boolean pretty) { | ||
super(new JSONSerializer(pretty)); | ||
} | ||
|
||
} |
47 changes: 47 additions & 0 deletions
47
api/src/main/java/com/glassdoor/planout4j/logging/AbstractSerializingLogger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.glassdoor.planout4j.logging; | ||
|
||
import java.util.Objects; | ||
|
||
import com.google.common.base.Converter; | ||
import com.typesafe.config.Config; | ||
|
||
|
||
/** | ||
* Base class for all serializing loggers. | ||
* @param <S> what to serialize to | ||
* @author ernest_mishkin | ||
*/ | ||
public abstract class AbstractSerializingLogger<S> implements Planout4jLogger { | ||
|
||
private static final String LOG_DEFAULT_EXPOSURE = "log_default_exposure"; | ||
|
||
protected final Converter<LogRecord, S> serializer; | ||
public volatile boolean logDefaultExposure; | ||
|
||
protected AbstractSerializingLogger(final Converter<LogRecord, S> serializer) { | ||
this.serializer = Objects.requireNonNull(serializer); | ||
} | ||
|
||
/** | ||
* Set certain properties from the configuration (<code>logging</code> section of the master planout4j.conf file) | ||
* @param config logging section config | ||
*/ | ||
@Override | ||
public void configure(final Config config) { | ||
logDefaultExposure = config.hasPath(LOG_DEFAULT_EXPOSURE) && config.getBoolean(LOG_DEFAULT_EXPOSURE); | ||
} | ||
|
||
/** | ||
* How to persist the serialized record | ||
* @param record serialized record | ||
*/ | ||
protected abstract void persist(S record); | ||
|
||
@Override | ||
public void exposed(final LogRecord record) { | ||
if (logDefaultExposure || record.namespace.getExperiment() != null) { | ||
persist(serializer.convert(record)); | ||
} | ||
} | ||
|
||
} |
55 changes: 55 additions & 0 deletions
55
api/src/main/java/com/glassdoor/planout4j/logging/JSONLoggingRecord.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.glassdoor.planout4j.logging; | ||
|
||
import java.util.Map; | ||
|
||
|
||
@SuppressWarnings("unused") | ||
class JSONLoggingRecord { | ||
|
||
String event; | ||
String timestamp; | ||
String namespace; | ||
String experiment; | ||
String salt; | ||
String checksum; | ||
Map inputs; | ||
Map overrides; | ||
Map params; | ||
|
||
public String getEvent() { | ||
return event; | ||
} | ||
|
||
public String getTimestamp() { | ||
return timestamp; | ||
} | ||
|
||
public String getNamespace() { | ||
return namespace; | ||
} | ||
|
||
public String getExperiment() { | ||
return experiment; | ||
} | ||
|
||
public String getSalt() { | ||
return salt; | ||
} | ||
|
||
public String getChecksum() { | ||
return checksum; | ||
} | ||
|
||
public Map getInputs() { | ||
return inputs; | ||
} | ||
|
||
public Map getOverrides() { | ||
return overrides; | ||
} | ||
|
||
public Map getParams() { | ||
return params; | ||
} | ||
|
||
} |
88 changes: 88 additions & 0 deletions
88
api/src/main/java/com/glassdoor/planout4j/logging/JSONSerializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package com.glassdoor.planout4j.logging; | ||
|
||
|
||
import java.text.DateFormat; | ||
import java.text.SimpleDateFormat; | ||
import java.util.Date; | ||
import java.util.TimeZone; | ||
|
||
import com.glassdoor.planout4j.Experiment; | ||
import com.google.common.base.Converter; | ||
import com.google.common.base.MoreObjects; | ||
import com.google.common.hash.HashFunction; | ||
import com.google.common.hash.Hashing; | ||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
|
||
|
||
/** | ||
* Serializes an exposure logging record into JSON string. | ||
* Format of the record follows <a href="https://facebook.github.io/planout/docs/logging.html">planout example</a> with minor tweaks. | ||
* Sample record:<pre> | ||
{ | ||
"event": "exposure", | ||
"timestamp": "2016-05-31T19:06:58.510Z", | ||
"namespace": "test_ns", | ||
"experiment": "def_exp", | ||
"checksum": "77341e05", | ||
"inputs": { | ||
"userid": 12345 | ||
}, | ||
"overrides": { | ||
}, | ||
"params": { | ||
"specific_goal": true, | ||
"group_size": 1, | ||
"ratings_per_user_goal": 64, | ||
"ratings_goal": 64 | ||
} | ||
} | ||
* </pre> | ||
* @author ernest_mishkin | ||
*/ | ||
public class JSONSerializer extends Converter<LogRecord, String> { | ||
|
||
private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); | ||
private static final DateFormat ISO = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); | ||
static { | ||
ISO.setTimeZone(UTC); | ||
} | ||
private static final HashFunction CHECKSUM = Hashing.crc32(); | ||
|
||
private final Gson gson; | ||
|
||
public JSONSerializer() { | ||
this(false); | ||
} | ||
|
||
public JSONSerializer(final boolean pretty) { | ||
final GsonBuilder builder = new GsonBuilder(); | ||
if (pretty) { | ||
builder.setPrettyPrinting(); | ||
} | ||
gson = builder.create(); | ||
} | ||
|
||
@Override | ||
protected String doForward(final LogRecord record) { | ||
final JSONLoggingRecord jlr = new JSONLoggingRecord(); | ||
jlr.event = "exposure"; | ||
jlr.timestamp = ISO.format(new Date()); | ||
jlr.namespace = record.namespace.getName(); | ||
jlr.salt = record.namespace.nsConf.salt; | ||
final Experiment exp = MoreObjects.firstNonNull(record.namespace.getExperiment(), record.namespace.nsConf.getDefaultExperiment()); | ||
jlr.experiment = exp.name; | ||
jlr.checksum = CHECKSUM.hashUnencodedChars(exp.def.getCopyOfScript().toString()).toString(); | ||
jlr.inputs = record.input; | ||
jlr.overrides = record.overrides; | ||
jlr.params = record.namespace.getParams(); | ||
return gson.toJson(jlr); | ||
} | ||
|
||
@Override | ||
protected LogRecord doBackward(final String s) { | ||
throw new IllegalStateException("Deserialization is not implemented"); | ||
} | ||
|
||
|
||
} |
60 changes: 60 additions & 0 deletions
60
api/src/main/java/com/glassdoor/planout4j/logging/LogRecord.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.glassdoor.planout4j.logging; | ||
|
||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
import com.glassdoor.planout4j.Namespace; | ||
import com.google.common.base.MoreObjects; | ||
import com.google.common.collect.ImmutableMap; | ||
|
||
|
||
/** | ||
* Represents data logged at exposure. | ||
* @author ernest_mishkin | ||
*/ | ||
public class LogRecord { | ||
|
||
public final Namespace namespace; | ||
public final Map<String, ?> input; | ||
public final Map<String, ?> overrides; | ||
|
||
public LogRecord(final Namespace namespace, final Map<String, ?> input, final Map<String, ?> overrides) { | ||
this.namespace = namespace; | ||
this.input = ImmutableMap.copyOf(input); | ||
//noinspection unchecked,CollectionsFieldAccessReplaceableByMethodCall | ||
this.overrides = overrides == null ? Collections.EMPTY_MAP : ImmutableMap.copyOf(overrides); | ||
} | ||
|
||
|
||
@Override | ||
public boolean equals(final Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
final LogRecord record = (LogRecord) o; | ||
return Objects.equals(namespace, record.namespace) && | ||
Objects.equals(input, record.input) && | ||
Objects.equals(overrides, record.overrides); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(namespace, input, overrides); | ||
} | ||
|
||
|
||
@Override | ||
public String toString() { | ||
return MoreObjects.toStringHelper(this) | ||
.add("namespace", namespace.getName()) | ||
.add("experiment", namespace.getExperiment().name) | ||
.add("input", input) | ||
.add("overrides", overrides) | ||
.toString(); | ||
} | ||
|
||
} |
39 changes: 39 additions & 0 deletions
39
api/src/main/java/com/glassdoor/planout4j/logging/Planout4jLogger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.glassdoor.planout4j.logging; | ||
|
||
import com.typesafe.config.Config; | ||
|
||
|
||
/** | ||
* Implementations of this interface are responsible for performing exposure logging. | ||
* If the logging involves more than trivial amount of time/resources, it should be done in a separate thread | ||
* as planout4j code itself will perform logging serially. | ||
* @author ernest_mishkin | ||
*/ | ||
public interface Planout4jLogger { | ||
|
||
/** | ||
* Invoked when input (e.g. a user) has been exposed to an experiment within a specific namespace. | ||
* Any runtime exceptions thrown within implementations will be caught and logged (but will <b>not</b> break the flow). | ||
* @param record exposure details | ||
*/ | ||
void exposed(LogRecord record); | ||
|
||
/** | ||
* Set certain properties from the configuration (<code>logging</code> section of the master planout4j.conf file) | ||
* @param config logging section config | ||
*/ | ||
void configure(Config config); | ||
|
||
|
||
/** | ||
* The "no-op" logger, does nothing. Default one for backwards-compatibility reasons. | ||
*/ | ||
Planout4jLogger NO_OP = new Planout4jLogger() { | ||
@Override | ||
public void exposed(final LogRecord record) {} | ||
@Override | ||
public void configure(final Config config) {} | ||
}; | ||
|
||
|
||
} |
Oops, something went wrong.