Skip to content

Commit de0c919

Browse files
committed
Cleanup logging; update local example
1 parent 774ca1b commit de0c919

File tree

6 files changed

+80
-26
lines changed

6 files changed

+80
-26
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ protobuf {
132132
}
133133

134134
ext {
135-
retrofit_version = "2.9.0"
135+
retrofit_version = "2.11.0"
136136
jackson_version = "2.15.3"
137137
swagger_annotations_version = "2.2.18"
138138
lombok_version = "1.18.30"
@@ -142,7 +142,7 @@ ext {
142142
mockito_core_version = "5.6.0"
143143
protobuf_version = "3.24.4"
144144
openfeature_version = "1.7.0"
145-
eventsource_version = "4.0.0"
145+
eventsource_version = "4.1.1"
146146
}
147147

148148
dependencies {

src/examples/java/com/devcycle/examples/LocalExample.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.devcycle.examples;
22

3+
import com.devcycle.sdk.server.common.logging.SimpleDevCycleLogger;
34
import com.devcycle.sdk.server.common.model.DevCycleUser;
45
import com.devcycle.sdk.server.local.api.DevCycleLocalClient;
56
import com.devcycle.sdk.server.local.model.DevCycleLocalOptions;
@@ -22,8 +23,13 @@ public static void main(String[] args) throws InterruptedException {
2223
// The default value can be of type string, boolean, number, or JSON
2324
Boolean defaultValue = false;
2425

25-
DevCycleLocalOptions options = DevCycleLocalOptions.builder().configPollingIntervalMs(60000)
26-
.disableAutomaticEventLogging(false).disableCustomEventLogging(false).enableBetaRealtimeUpdates(true).build();
26+
DevCycleLocalOptions options = DevCycleLocalOptions.builder()
27+
.configPollingIntervalMS(60000)
28+
.disableAutomaticEventLogging(false)
29+
.customLogger(new SimpleDevCycleLogger(SimpleDevCycleLogger.Level.DEBUG))
30+
.disableCustomEventLogging(false)
31+
.enableBetaRealtimeUpdates(true)
32+
.build();
2733

2834
// Initialize DevCycle Client
2935
DevCycleLocalClient client = new DevCycleLocalClient(server_sdk_key, options);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ public class SSE {
1515
private String hostname;
1616
private String path;
1717
}
18+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.devcycle.sdk.server.common.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
9+
@Data
10+
@Builder
11+
@AllArgsConstructor
12+
@NoArgsConstructor
13+
@JsonIgnoreProperties(ignoreUnknown = true)
14+
public class SSEMessage {
15+
private String etag;
16+
private double lastModified;
17+
private String type;
18+
}

src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import com.devcycle.sdk.server.common.api.IDevCycleApi;
44
import com.devcycle.sdk.server.common.exception.DevCycleException;
55
import com.devcycle.sdk.server.common.logging.DevCycleLogger;
6-
import com.devcycle.sdk.server.common.model.ErrorResponse;
7-
import com.devcycle.sdk.server.common.model.HttpResponseCode;
8-
import com.devcycle.sdk.server.common.model.ProjectConfig;
6+
import com.devcycle.sdk.server.common.model.*;
97
import com.devcycle.sdk.server.local.api.DevCycleLocalApiClient;
108
import com.devcycle.sdk.server.local.bucketing.LocalBucketing;
119
import com.devcycle.sdk.server.local.model.DevCycleLocalOptions;
@@ -31,10 +29,11 @@ public final class EnvironmentConfigManager {
3129
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
3230
private static final int DEFAULT_POLL_INTERVAL_MS = 30000;
3331
private static final int MIN_INTERVALS_MS = 1000;
34-
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory());
32+
private ScheduledExecutorService scheduler;
3533
private final IDevCycleApi configApiClient;
3634
private final LocalBucketing localBucketing;
3735
private SSEManager sseManager;
36+
private boolean isSSEConnected = false;
3837
private final DevCycleLocalOptions options;
3938

4039
private ProjectConfig config;
@@ -57,11 +56,12 @@ public EnvironmentConfigManager(String sdkKey, LocalBucketing localBucketing, De
5756
pollingIntervalMS = configPollingIntervalMS >= MIN_INTERVALS_MS ? configPollingIntervalMS
5857
: DEFAULT_POLL_INTERVAL_MS;
5958

60-
setupScheduler();
59+
scheduler = setupScheduler();
60+
scheduler.scheduleAtFixedRate(getConfigRunnable, 0, this.pollingIntervalMS, TimeUnit.MILLISECONDS);
6161
}
6262

63-
private void setupScheduler() {
64-
scheduler.scheduleAtFixedRate(getConfigRunnable, 0, this.pollingIntervalMS, TimeUnit.MILLISECONDS);
63+
private ScheduledExecutorService setupScheduler() {
64+
return Executors.newScheduledThreadPool(1, new DaemonThreadFactory());
6565
}
6666

6767
private Runnable getConfigRunnable = new Runnable() {
@@ -99,6 +99,24 @@ private ProjectConfig getConfig() throws DevCycleException {
9999

100100
private Void handleSSEMessage(MessageEvent messageEvent) {
101101
DevCycleLogger.debug("Received message: " + messageEvent.getData());
102+
if (!isSSEConnected)
103+
{
104+
handleSSEStarted(null);
105+
}
106+
107+
String data = messageEvent.getData();
108+
if (data == null || data.isEmpty() || data.equals("keepalive")) {
109+
return null;
110+
}
111+
try {
112+
SSEMessage message = OBJECT_MAPPER.readValue(data, SSEMessage.class);
113+
if (message.getType() == null || message.getType().equals("refetchConfig") || message.getType().isEmpty()) {
114+
DevCycleLogger.debug("Received refetchConfig message, fetching new config");
115+
getConfigRunnable.run();
116+
}
117+
} catch (JsonProcessingException e) {
118+
DevCycleLogger.warning("Failed to parse SSE message: " + e.getMessage());
119+
}
102120
return null;
103121
}
104122

@@ -108,8 +126,10 @@ private Void handleSSEError(FaultEvent faultEvent) {
108126
}
109127

110128
private Void handleSSEStarted(StartedEvent startedEvent) {
129+
isSSEConnected = true;
111130
DevCycleLogger.debug("SSE Connected - setting polling interval to " + pollingIntervalSSEMS);
112-
scheduler.shutdown();
131+
scheduler.close();
132+
scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory());
113133
scheduler.scheduleAtFixedRate(getConfigRunnable, 0, pollingIntervalSSEMS, TimeUnit.MILLISECONDS);
114134
return null;
115135
}
@@ -239,10 +259,12 @@ private ProjectConfig getConfigResponse(Call<ProjectConfig> call) throws DevCycl
239259

240260
private void stopPolling() {
241261
pollingEnabled = false;
262+
242263
scheduler.shutdown();
243264
}
244265

245266
public void cleanup() {
267+
sseManager.close();
246268
stopPolling();
247269
}
248270
}

src/main/java/com/devcycle/sdk/server/local/managers/SSEManager.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99

1010
public class SSEManager {
1111

12-
private static EventSource eventSource;
12+
private EventSource eventSource;
1313
private static Thread messageHandlerThread;
1414
private URI uri;
1515

1616
public SSEManager(URI uri) {
17-
eventSource = new EventSource.Builder(uri)
18-
.errorStrategy(ErrorStrategy.alwaysContinue())
19-
.retryDelay(10000, TimeUnit.MILLISECONDS)
20-
.build();
17+
this.eventSource = buildEventSource(uri);
18+
2119
this.uri = uri;
2220
}
2321

@@ -30,16 +28,25 @@ public void restart(URI uri, Function<MessageEvent, Void> messageHandler, Functi
3028
return;
3129
}
3230
this.uri = uri;
33-
if (eventSource != null&& eventSource.getState() == ReadyState.OPEN) {
31+
if (eventSource != null && eventSource.getState() == ReadyState.OPEN) {
3432
eventSource.close();
3533
}
3634
if (messageHandlerThread != null) {
3735
messageHandlerThread.interrupt();
3836
}
39-
eventSource = new EventSource.Builder(uri).build();
37+
eventSource = buildEventSource(uri);
4038
start(messageHandler, errorHandler, stateHandler);
4139
}
4240

41+
private EventSource buildEventSource(URI uri) {
42+
return new EventSource.Builder(ConnectStrategy.http(uri).clientBuilderActions(clientBuilder ->
43+
clientBuilder
44+
.connectTimeout(100 * 60, TimeUnit.SECONDS)
45+
.readTimeout(100 * 60, TimeUnit.SECONDS)
46+
.writeTimeout(100 * 60, TimeUnit.SECONDS)
47+
)).build();
48+
}
49+
4350
private boolean start(Function<MessageEvent, Void> messageHandler, Function<FaultEvent, Void> errorHandler, Function<StartedEvent, Void> stateHandler) {
4451
switch (eventSource.getState()) {
4552
case CONNECTING:
@@ -64,11 +71,13 @@ private static class SSEMessageHandler implements Runnable {
6471

6572
private final Function<MessageEvent, Void> messageHandler;
6673
private final Function<FaultEvent, Void> errorHandler;
74+
private final Function<StartedEvent, Void> stateHandler;
6775
private final EventSource sse;
6876

6977
public SSEMessageHandler(EventSource sse, Function<MessageEvent, Void> messageHandler, Function<FaultEvent, Void> errorHandler, Function<StartedEvent, Void> stateHandler) {
7078
this.messageHandler = messageHandler;
7179
this.errorHandler = errorHandler;
80+
this.stateHandler = stateHandler;
7281
this.sse = sse;
7382
}
7483

@@ -78,20 +87,18 @@ public void run() {
7887
try {
7988
StreamEvent event = sse.readAnyEvent();
8089
if (event instanceof MessageEvent) {
81-
DevCycleLogger.info("Message event received: " + ((MessageEvent) event).getData());
8290
messageHandler.apply((MessageEvent) event);
8391
} else if (event instanceof FaultEvent) {
84-
DevCycleLogger.error("Fault event received: " + ((FaultEvent) event).getCause().getMessage());
8592
errorHandler.apply((FaultEvent) event);
8693
} else if (event instanceof StartedEvent) {
87-
DevCycleLogger.info("Started event received");
88-
} else if (event instanceof CommentEvent){
89-
DevCycleLogger.info("Comment event received: " + ((CommentEvent) event).getText());
94+
stateHandler.apply((StartedEvent) event);
95+
} else if (event instanceof CommentEvent) {
96+
messageHandler.apply(new MessageEvent(((CommentEvent) event).getText()));
9097
} else {
91-
DevCycleLogger.error("Unknown event type: " + event.getClass().getName());
98+
DevCycleLogger.warning("Unknown event type: " + event.getClass().getName());
9299
}
93100
} catch (StreamException e) {
94-
DevCycleLogger.error("Error reading event", e);
101+
DevCycleLogger.warning("Error reading event");
95102
DevCycleLogger.warning(e.getMessage());
96103
}
97104
}

0 commit comments

Comments
 (0)