Skip to content

Commit ec8cab9

Browse files
Add customizable logging system (#77)
* swapped System.out for a Logger * removed logger that is problematic in this class * switch to an internal logging system, to avoid pinning to a framework * ability to set logger for cloud client * logger instructions * polishing the feature
1 parent 7a03c56 commit ec8cab9

File tree

13 files changed

+217
-35
lines changed

13 files changed

+217
-35
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,46 @@ public class MyClass {
7979

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

82+
## Logging
83+
84+
The DevCycle SDK logs to **stdout** by default and does not require any specific logging package. To integrate with your
85+
own logging system, such as Java Logging or SLF4J, you can create a wrapper that implements the `IDVCLogger` interface.
86+
Then you can set the logger into the Java Server SDK setting the Custom Logger property in the options object used to
87+
initialize the client.
88+
89+
```java
90+
91+
```java
92+
// Create your logging wrapper
93+
IDVCLogger loggingWrapper = new IDVCLogger() {
94+
@Override
95+
public void debug(String message) {
96+
// Your logging implementation here
97+
}
98+
99+
@Override
100+
public void info(String message) {
101+
// Your logging implementation here
102+
}
103+
104+
@Override
105+
public void warn(String message) {
106+
// Your logging implementation here
107+
}
108+
109+
@Override
110+
public void error(String message) {
111+
// Your logging implementation here
112+
}
113+
};
114+
115+
// Set the logger in the options before creating the DVCLocalClient
116+
DVCLocalOptions options = DVCLocalOptions.builder().customLogger(loggingWrapper).build();
117+
DVCLocalClient dvcClient = new DVCLocalClient("YOUR_DVC_SERVER_SDK_KEY", options);
118+
119+
// Or for DVCCloudClient
120+
DVCCloudOptions options = DVCCloudOptions.builder().customLogger(loggingWrapper).build();
121+
DVCCloudClient dvcClient = new DVCCloudClient("YOUR_DVC_SERVER_SDK_KEY", options);
122+
```
123+
124+
You can also disable all logging by setting the custom logger to `new SimpleDVCLogger(SimpleDVCLogger.Level.OFF)`.

src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.devcycle.sdk.server.cloud.model.DVCCloudOptions;
44
import com.devcycle.sdk.server.common.api.IDVCApi;
55
import com.devcycle.sdk.server.common.exception.DVCException;
6+
import com.devcycle.sdk.server.common.logging.DVCLogger;
67
import com.devcycle.sdk.server.common.model.*;
78
import com.devcycle.sdk.server.common.model.Variable.TypeEnum;
89
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -37,6 +38,11 @@ public DVCCloudClient(String sdkKey, DVCCloudOptions options) {
3738
throw new IllegalArgumentException("Invalid environment key provided. Please call initialize with a valid server environment key");
3839
}
3940

41+
if(options.getCustomLogger() != null)
42+
{
43+
DVCLogger.setCustomLogger(options.getCustomLogger());
44+
}
45+
4046
this.dvcOptions = options;
4147
api = new DVCCloudApiClient(sdkKey, options).initialize();
4248
OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);

src/main/java/com/devcycle/sdk/server/cloud/model/DVCCloudOptions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.devcycle.sdk.server.cloud.model;
22

3+
import com.devcycle.sdk.server.common.logging.IDVCLogger;
34
import com.devcycle.sdk.server.common.model.IDVCOptions;
45

56
import lombok.Builder;
@@ -14,5 +15,8 @@ public class DVCCloudOptions {
1415
@Builder.Default
1516
private String baseURLOverride = null;
1617

18+
@Builder.Default
19+
private IDVCLogger customLogger = null;
20+
1721
public static class DVCCloudOptionsBuilder implements IDVCOptions { }
1822
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.devcycle.sdk.server.common.logging;
2+
/**
3+
* DVCLogger is a simple central entrypoint for the SDK to log messages without pinning the SDK to a
4+
* specific logging framework. By default it logs to stdout but can e overriden by calling setCustomLogger()
5+
*/
6+
public class DVCLogger {
7+
private static IDVCLogger logger = new SimpleDVCLogger(SimpleDVCLogger.Level.INFO);
8+
public static void setCustomLogger(IDVCLogger logger) {
9+
DVCLogger.logger = logger;
10+
}
11+
12+
public static void debug(String message) {
13+
if(logger != null){
14+
logger.debug(message);
15+
}
16+
}
17+
18+
public static void info(String message) {
19+
if(logger != null) {
20+
logger.info(message);
21+
}
22+
}
23+
24+
public static void warning(String message) {
25+
if(logger != null) {
26+
logger.warning(message);
27+
}
28+
}
29+
30+
public static void error(String message) {
31+
if(logger != null) {
32+
logger.error(message);
33+
}
34+
}
35+
36+
public static void error(String message, Throwable t) {
37+
if(logger != null) {
38+
logger.error(message, t);
39+
}
40+
}
41+
}
42+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.devcycle.sdk.server.common.logging;
2+
3+
/**
4+
* A simple interface for logging inside the SDK. Implement this interface and pass it to the SDK to override the
5+
* default behavior. Use this interface to integrate with an existing logging framework such as Java Logging, Log4j or SLF4J
6+
*/
7+
public interface IDVCLogger {
8+
void debug(String message);
9+
void info(String message);
10+
void warning(String message);
11+
void error(String message);
12+
void error(String message, Throwable t);
13+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.devcycle.sdk.server.common.logging;
2+
3+
/**
4+
* Basic implementation of IDVCLogger that logs to stdout with some basic log level filtering.
5+
*/
6+
public class SimpleDVCLogger implements IDVCLogger {
7+
public enum Level {
8+
DEBUG,
9+
INFO,
10+
WARNING,
11+
ERROR,
12+
OFF,
13+
}
14+
private Level level;
15+
public SimpleDVCLogger(Level level) {
16+
this.level = level;
17+
}
18+
19+
@Override
20+
public void debug(String message) {
21+
if(this.level.ordinal() == Level.DEBUG.ordinal())
22+
{
23+
System.out.println(message);
24+
}
25+
}
26+
27+
@Override
28+
public void info(String message) {
29+
if(this.level.ordinal() <= Level.INFO.ordinal()) {
30+
System.out.println(message);
31+
}
32+
}
33+
34+
@Override
35+
public void warning(String message) {
36+
if(this.level.ordinal() <= Level.WARNING.ordinal()) {
37+
System.out.println(message);
38+
}
39+
}
40+
41+
@Override
42+
public void error(String message) {
43+
if(this.level.ordinal() <= Level.ERROR.ordinal()) {
44+
System.out.println(message);
45+
}
46+
}
47+
48+
@Override
49+
public void error(String message, Throwable t) {
50+
if(this.level.ordinal() <= Level.ERROR.ordinal()) {
51+
System.out.println(message);
52+
}
53+
}
54+
}

src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.devcycle.sdk.server.common.model;
22

3+
import com.devcycle.sdk.server.common.logging.DVCLogger;
34
import com.fasterxml.jackson.annotation.JsonInclude;
45
import com.fasterxml.jackson.annotation.JsonValue;
56
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -11,11 +12,12 @@
1112

1213
import java.net.InetAddress;
1314
import java.net.UnknownHostException;
15+
import java.util.logging.Level;
16+
import java.util.logging.Logger;
1417

1518
@Data
1619
@Builder
1720
@JsonInclude(JsonInclude.Include.NON_NULL)
18-
1921
public class PlatformData {
2022
public PlatformData(String platform, String platformVersion, SdkTypeEnum sdkType, String sdkVersion, String hostname) {
2123
this.platform = platform;
@@ -25,7 +27,7 @@ public PlatformData(String platform, String platformVersion, SdkTypeEnum sdkType
2527
try {
2628
this.hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName();
2729
} catch (UnknownHostException e) {
28-
System.out.println("Error getting hostname: " + e.getMessage());
30+
DVCLogger.warning("Error getting system hostname: " + e.getMessage());
2931
this.hostname = "";
3032
}
3133
}
@@ -64,7 +66,7 @@ public String toString() {
6466
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
6567
platformDataString = mapper.writeValueAsString(platformData);
6668
} catch (JsonProcessingException e) {
67-
System.out.println("Error reading platformData: " + e.getMessage());
69+
DVCLogger.warning("Error reading platformData: " + e.getMessage());
6870
}
6971
return platformDataString;
7072
}

src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.devcycle.sdk.server.local.protobuf.SDKVariable_PB;
1111
import com.devcycle.sdk.server.local.protobuf.VariableForUserParams_PB;
1212
import com.devcycle.sdk.server.local.protobuf.VariableType_PB;
13+
import com.devcycle.sdk.server.common.logging.DVCLogger;
1314
import com.devcycle.sdk.server.local.utils.ProtobufUtils;
1415
import com.fasterxml.jackson.core.JsonProcessingException;
1516
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -27,7 +28,6 @@ public final class DVCLocalClient {
2728

2829
private EventQueueManager eventQueueManager;
2930

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

43+
if(dvcOptions.getCustomLogger() != null) {
44+
DVCLogger.setCustomLogger(dvcOptions.getCustomLogger());
45+
}
46+
4347
if(!isValidRuntime()){
44-
System.out.println("Invalid architecture. The DVCLocalClient requires a 64-bit, x86 runtime environment.");
48+
DVCLogger.warning("Invalid architecture. The DVCLocalClient requires a 64-bit, x86 runtime environment.");
4549
}
4650

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

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

132136
if (!isInitialized()) {
133-
System.out.println("Variable called before DVCClient has initialized, returning default value");
137+
DVCLogger.info("Variable called before DVCClient has initialized, returning default value");
134138
try {
135139
eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableDefaulted").target(key).build(), null);
136140
} catch (Exception e) {
137-
System.out.printf("Unable to parse aggVariableDefaulted event for Variable %s due to error: %s", key, e.toString());
141+
DVCLogger.error("Unable to parse aggVariableDefaulted event for Variable " + key + " due to error: " + e, e);
138142
}
139143
return defaultVariable;
140144
}
@@ -157,13 +161,13 @@ public <T> Variable<T> variable(User user, String key, T defaultValue) {
157161
} else {
158162
SDKVariable_PB sdkVariable = SDKVariable_PB.parseFrom(variableData);
159163
if(sdkVariable.getType() != pbVariableType) {
160-
System.out.printf("Variable type mismatch, returning default value");
164+
DVCLogger.info("Variable type mismatch, returning default value");
161165
return defaultVariable;
162166
}
163167
return ProtobufUtils.createVariable(sdkVariable, defaultValue);
164168
}
165169
} catch (Exception e) {
166-
System.out.printf("Unable to evaluate Variable %s due to error: %s", key, e);
170+
DVCLogger.error("Unable to evaluate Variable " + key + " due to error: " + e, e);
167171
}
168172
return defaultVariable;
169173
}
@@ -185,7 +189,7 @@ public Map<String, BaseVariable> allVariables(User user) {
185189
try {
186190
bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user);
187191
} catch (JsonProcessingException e) {
188-
System.out.printf("Unable to parse JSON for allVariables due to error: %s%n", e.getMessage());
192+
DVCLogger.info("Unable to parse JSON for allVariables due to error: " + e.getMessage());
189193
return Collections.emptyMap();
190194
}
191195
return bucketedUserConfig.variables;
@@ -207,14 +211,14 @@ public void track(User user, Event event) {
207211
try {
208212
eventQueueManager.queueEvent(user, event);
209213
} catch (Exception e) {
210-
System.out.printf("Failed to queue event due to error: %s%n", e.getMessage());
214+
DVCLogger.warning("Failed to queue event due to error: " + e.getMessage());
211215
}
212216
}
213217

214218
public void setClientCustomData(Map<String,Object> customData) {
215219
if (!isInitialized())
216220
{
217-
System.out.println("SetClientCustomData called before DVCClient has initialized");
221+
DVCLogger.info("SetClientCustomData called before DVCClient has initialized");
218222
return;
219223
}
220224

@@ -224,7 +228,7 @@ public void setClientCustomData(Map<String,Object> customData) {
224228
String customDataJSON = mapper.writeValueAsString(customData);
225229
localBucketing.setClientCustomData(this.sdkKey, customDataJSON);
226230
} catch(Exception e) {
227-
System.out.printf("Failed to set custom data: %s%n", e.getMessage());
231+
DVCLogger.error("Failed to set custom data due to error: " + e.getMessage(), e);
228232
}
229233
}
230234
}

src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.devcycle.sdk.server.local.bucketing;
22

3+
import com.devcycle.sdk.server.common.logging.DVCLogger;
34
import com.devcycle.sdk.server.common.model.User;
45
import com.devcycle.sdk.server.common.model.Variable;
56
import com.devcycle.sdk.server.local.model.BucketedUserConfig;
@@ -20,6 +21,8 @@
2021
import java.text.SimpleDateFormat;
2122
import java.util.*;
2223
import java.util.concurrent.atomic.AtomicReference;
24+
import java.util.logging.Level;
25+
import java.util.logging.Logger;
2326

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

44+
private Logger logger = Logger.getLogger(LocalBucketing.class.getName());
45+
4146
public LocalBucketing() {
4247
OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
4348

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

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

0 commit comments

Comments
 (0)