Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,46 @@ public class MyClass {

To find usage documentation, visit our docs for [Local Bucketing](https://docs.devcycle.com/docs/sdk/server-side-sdks/java-local).

## Logging

The DevCycle SDK logs to **stdout** by default and does not require any specific logging package. To integrate with your
own logging system, such as Java Logging or SLF4J, you can create a wrapper that implements the `IDVCLogger` interface.
Then you can set the logger into the Java Server SDK setting the Custom Logger property in the options object used to
initialize the client.

```java

```java
// Create your logging wrapper
IDVCLogger loggingWrapper = new IDVCLogger() {
@Override
public void debug(String message) {
// Your logging implementation here
}

@Override
public void info(String message) {
// Your logging implementation here
}

@Override
public void warn(String message) {
// Your logging implementation here
}

@Override
public void error(String message) {
// Your logging implementation here
}
};

// Set the logger in the options before creating the DVCLocalClient
DVCLocalOptions options = DVCLocalOptions.builder().customLogger(loggingWrapper).build();
DVCLocalClient dvcClient = new DVCLocalClient("YOUR_DVC_SERVER_SDK_KEY", options);

// Or for DVCCloudClient
DVCCloudOptions options = DVCCloudOptions.builder().customLogger(loggingWrapper).build();
DVCCloudClient dvcClient = new DVCCloudClient("YOUR_DVC_SERVER_SDK_KEY", options);
```

You can also disable all logging by setting the custom logger to `new SimpleDVCLogger(SimpleDVCLogger.Level.OFF)`.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.devcycle.sdk.server.cloud.model.DVCCloudOptions;
import com.devcycle.sdk.server.common.api.IDVCApi;
import com.devcycle.sdk.server.common.exception.DVCException;
import com.devcycle.sdk.server.common.logging.DVCLogger;
import com.devcycle.sdk.server.common.model.*;
import com.devcycle.sdk.server.common.model.Variable.TypeEnum;
import com.fasterxml.jackson.annotation.JsonInclude;
Expand Down Expand Up @@ -37,6 +38,11 @@ public DVCCloudClient(String sdkKey, DVCCloudOptions options) {
throw new IllegalArgumentException("Invalid environment key provided. Please call initialize with a valid server environment key");
}

if(options.getCustomLogger() != null)
{
DVCLogger.setCustomLogger(options.getCustomLogger());
}

this.dvcOptions = options;
api = new DVCCloudApiClient(sdkKey, options).initialize();
OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devcycle.sdk.server.cloud.model;

import com.devcycle.sdk.server.common.logging.IDVCLogger;
import com.devcycle.sdk.server.common.model.IDVCOptions;

import lombok.Builder;
Expand All @@ -14,5 +15,8 @@ public class DVCCloudOptions {
@Builder.Default
private String baseURLOverride = null;

@Builder.Default
private IDVCLogger customLogger = null;

public static class DVCCloudOptionsBuilder implements IDVCOptions { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.devcycle.sdk.server.common.logging;
/**
* DVCLogger is a simple central entrypoint for the SDK to log messages without pinning the SDK to a
* specific logging framework. By default it logs to stdout but can e overriden by calling setCustomLogger()
*/
public class DVCLogger {
private static IDVCLogger logger = new SimpleDVCLogger(SimpleDVCLogger.Level.INFO);
public static void setCustomLogger(IDVCLogger logger) {
DVCLogger.logger = logger;
}

public static void debug(String message) {
if(logger != null){
logger.debug(message);
}
}

public static void info(String message) {
if(logger != null) {
logger.info(message);
}
}

public static void warning(String message) {
if(logger != null) {
logger.warning(message);
}
}

public static void error(String message) {
if(logger != null) {
logger.error(message);
}
}

public static void error(String message, Throwable t) {
if(logger != null) {
logger.error(message, t);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.devcycle.sdk.server.common.logging;

/**
* A simple interface for logging inside the SDK. Implement this interface and pass it to the SDK to override the
* default behavior. Use this interface to integrate with an existing logging framework such as Java Logging, Log4j or SLF4J
*/
public interface IDVCLogger {
void debug(String message);
void info(String message);
void warning(String message);
void error(String message);
void error(String message, Throwable t);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.devcycle.sdk.server.common.logging;

/**
* Basic implementation of IDVCLogger that logs to stdout with some basic log level filtering.
*/
public class SimpleDVCLogger implements IDVCLogger {
public enum Level {
DEBUG,
INFO,
WARNING,
ERROR,
OFF,
}
private Level level;
public SimpleDVCLogger(Level level) {
this.level = level;
}

@Override
public void debug(String message) {
if(this.level.ordinal() == Level.DEBUG.ordinal())
{
System.out.println(message);
}
}

@Override
public void info(String message) {
if(this.level.ordinal() <= Level.INFO.ordinal()) {
System.out.println(message);
}
}

@Override
public void warning(String message) {
if(this.level.ordinal() <= Level.WARNING.ordinal()) {
System.out.println(message);
}
}

@Override
public void error(String message) {
if(this.level.ordinal() <= Level.ERROR.ordinal()) {
System.out.println(message);
}
}

@Override
public void error(String message, Throwable t) {
if(this.level.ordinal() <= Level.ERROR.ordinal()) {
System.out.println(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devcycle.sdk.server.common.model;

import com.devcycle.sdk.server.common.logging.DVCLogger;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand All @@ -11,11 +12,12 @@

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;

@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)

public class PlatformData {
public PlatformData(String platform, String platformVersion, SdkTypeEnum sdkType, String sdkVersion, String hostname) {
this.platform = platform;
Expand All @@ -25,7 +27,7 @@ public PlatformData(String platform, String platformVersion, SdkTypeEnum sdkType
try {
this.hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
System.out.println("Error getting hostname: " + e.getMessage());
DVCLogger.warning("Error getting system hostname: " + e.getMessage());
this.hostname = "";
}
}
Expand Down Expand Up @@ -64,7 +66,7 @@ public String toString() {
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
platformDataString = mapper.writeValueAsString(platformData);
} catch (JsonProcessingException e) {
System.out.println("Error reading platformData: " + e.getMessage());
DVCLogger.warning("Error reading platformData: " + e.getMessage());
}
return platformDataString;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.devcycle.sdk.server.local.protobuf.SDKVariable_PB;
import com.devcycle.sdk.server.local.protobuf.VariableForUserParams_PB;
import com.devcycle.sdk.server.local.protobuf.VariableType_PB;
import com.devcycle.sdk.server.common.logging.DVCLogger;
import com.devcycle.sdk.server.local.utils.ProtobufUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -27,7 +28,6 @@ public final class DVCLocalClient {

private EventQueueManager eventQueueManager;


public DVCLocalClient(String sdkKey) {
this(sdkKey, DVCLocalOptions.builder().build());
}
Expand All @@ -40,8 +40,12 @@ public DVCLocalClient(String sdkKey, DVCLocalOptions dvcOptions) {
throw new IllegalArgumentException("Invalid SDK key provided. Please call initialize with a valid server SDK key");
}

if(dvcOptions.getCustomLogger() != null) {
DVCLogger.setCustomLogger(dvcOptions.getCustomLogger());
}

if(!isValidRuntime()){
System.out.println("Invalid architecture. The DVCLocalClient requires a 64-bit, x86 runtime environment.");
DVCLogger.warning("Invalid architecture. The DVCLocalClient requires a 64-bit, x86 runtime environment.");
}

localBucketing.setPlatformData(PlatformData.builder().build().toString());
Expand All @@ -51,7 +55,7 @@ public DVCLocalClient(String sdkKey, DVCLocalOptions dvcOptions) {
try {
eventQueueManager = new EventQueueManager(sdkKey, localBucketing, dvcOptions);
} catch (Exception e) {
System.out.printf("Error creating event queue due to error: %s%n", e.getMessage());
DVCLogger.warning("Error creating event queue due to error: " + e.getMessage());
}
}

Expand Down Expand Up @@ -81,7 +85,7 @@ public Map<String, Feature> allFeatures(User user) {
try {
bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user);
} catch (JsonProcessingException e) {
System.out.printf("Unable to parse JSON for allFeatures due to error: %s%n", e.getMessage());
DVCLogger.info("Unable to parse JSON for allFeatures due to error: " + e.getMessage());
return Collections.emptyMap();
}
return bucketedUserConfig.features;
Expand Down Expand Up @@ -130,11 +134,11 @@ public <T> Variable<T> variable(User user, String key, T defaultValue) {
.build();

if (!isInitialized()) {
System.out.println("Variable called before DVCClient has initialized, returning default value");
DVCLogger.info("Variable called before DVCClient has initialized, returning default value");
try {
eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableDefaulted").target(key).build(), null);
} catch (Exception e) {
System.out.printf("Unable to parse aggVariableDefaulted event for Variable %s due to error: %s", key, e.toString());
DVCLogger.error("Unable to parse aggVariableDefaulted event for Variable " + key + " due to error: " + e, e);
}
return defaultVariable;
}
Expand All @@ -157,13 +161,13 @@ public <T> Variable<T> variable(User user, String key, T defaultValue) {
} else {
SDKVariable_PB sdkVariable = SDKVariable_PB.parseFrom(variableData);
if(sdkVariable.getType() != pbVariableType) {
System.out.printf("Variable type mismatch, returning default value");
DVCLogger.info("Variable type mismatch, returning default value");
return defaultVariable;
}
return ProtobufUtils.createVariable(sdkVariable, defaultValue);
}
} catch (Exception e) {
System.out.printf("Unable to evaluate Variable %s due to error: %s", key, e);
DVCLogger.error("Unable to evaluate Variable " + key + " due to error: " + e, e);
}
return defaultVariable;
}
Expand All @@ -185,7 +189,7 @@ public Map<String, BaseVariable> allVariables(User user) {
try {
bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user);
} catch (JsonProcessingException e) {
System.out.printf("Unable to parse JSON for allVariables due to error: %s%n", e.getMessage());
DVCLogger.info("Unable to parse JSON for allVariables due to error: " + e.getMessage());
return Collections.emptyMap();
}
return bucketedUserConfig.variables;
Expand All @@ -207,14 +211,14 @@ public void track(User user, Event event) {
try {
eventQueueManager.queueEvent(user, event);
} catch (Exception e) {
System.out.printf("Failed to queue event due to error: %s%n", e.getMessage());
DVCLogger.warning("Failed to queue event due to error: " + e.getMessage());
}
}

public void setClientCustomData(Map<String,Object> customData) {
if (!isInitialized())
{
System.out.println("SetClientCustomData called before DVCClient has initialized");
DVCLogger.info("SetClientCustomData called before DVCClient has initialized");
return;
}

Expand All @@ -224,7 +228,7 @@ public void setClientCustomData(Map<String,Object> customData) {
String customDataJSON = mapper.writeValueAsString(customData);
localBucketing.setClientCustomData(this.sdkKey, customDataJSON);
} catch(Exception e) {
System.out.printf("Failed to set custom data: %s%n", e.getMessage());
DVCLogger.error("Failed to set custom data due to error: " + e.getMessage(), e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devcycle.sdk.server.local.bucketing;

import com.devcycle.sdk.server.common.logging.DVCLogger;
import com.devcycle.sdk.server.common.model.User;
import com.devcycle.sdk.server.common.model.Variable;
import com.devcycle.sdk.server.local.model.BucketedUserConfig;
Expand All @@ -20,6 +21,8 @@
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import static io.github.kawamuray.wasmtime.WasmValType.F64;
import static io.github.kawamuray.wasmtime.WasmValType.I32;
Expand All @@ -38,6 +41,8 @@ public class LocalBucketing {
private final int WASM_OBJECT_ID_STRING = 1;
private final int WASM_OBJECT_ID_UINT8ARRAY = 9;

private Logger logger = Logger.getLogger(LocalBucketing.class.getName());

public LocalBucketing() {
OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);

Expand Down Expand Up @@ -76,7 +81,7 @@ private Collection<Extern> setImportsOnLinker() {

Func consoleLogFn = WasmFunctions.wrap(store, I32, (addr) -> {
String message = readWasmString(((Number) addr).intValue());
System.out.println(message);
DVCLogger.warning("WASM error: " + message);
});
linker.define("env", "console.log", Extern.fromFunc(consoleLogFn));

Expand Down
Loading