Skip to content

Commit 44ebdb5

Browse files
LaunchDarklyReleaseBoteli-darklyLaunchDarklyCIgwhelanLDLaunchDarklyReleaseBot
authored
prepare 2.7.0 release (#73)
* add time threshold for backoff reset * allow endpoint to be specified as either URI or HttpUrl * add @SInCE * add interface for customizing requests * javadoc fixes * add changelog for past releases * remove JSR305 * replace SSL-specific config method with general-purpose HTTP config method * make helper method static * add end-to-end EventSource tests * spacing * omit default header value if there's a custom value * avoid trailing period in logger name * add 1.x branch * update to OkHttp 4.x and Java 8 * javadoc fixes * remove EventSource setters, + test improvements * update Gradle release * enable Github Pages * skip tests in release * add ability to force a stream restart; improve tests so we can test this * revert whitespace change * add 1.x branch * bump OkHttp version to latest Java 7-compatible version * prepare 1.10.2 release (#43) * Releasing version 1.10.2 * Gradle update + build fixes * add ability to force a stream restart; improve tests so we can test this (#29) * prepare 1.11.0 release (#44) * Releasing version 1.11.0 * Gradle release fixes * update to okhttp 4.5.0 * longer timeout for cleaner shutdown of test servers * fix Gradle scopes * allow setting specific thread priority * remove misleading logging & unnecessary backoff, improve tests (#34) * known issue with onClose() - add comment, disable test assertions * allow caller to specify a custom logger instead of SLF4J (#32) * add method for changing base name of SLF4J logger * enable coverage reports in CI, improve CI to test all supported Java versions * rm inapplicable CI copy-paste * another CI fix * add checkstyle config * fix jitter calculation when upper bound is a power of 2 * prepare 1.11.1 release (#49) * Releasing version 1.11.1 * misc coverage + test improvements, add CI enforcement of coverage (#39) * fix shutdown state logic, simplify code paths (#40) * Fix Java 7 compatibility. * add OpenJDK 7 build + fix test race condition + javadoc fix (#42) * prepare 1.11.2 release (#56) * Releasing version 1.11.2 * update Gradle to 6.8.3 * Kotlinize build script * fix logic for shutting down after an unrecoverable error * use newer HTTP test helpers * use Releaser v2 config + newer CI images (#47) * use new stream-reading implementation to support CR-only line endings * make buffer size configurable * rm usage that's not allowed in Java 8 * add Guava test dependency * add code coverage ovverride * implement contract tests (#48) * use Gradle 7 * Bounded queues for the EventHandler thread (#58) * Bounded queue for the EventHandler thread The unbounded queue fronting the 'event' thread can cause trouble when the EventHandler is unable to keep up with the workload. This can lead to heap exhaustion, GC issues and failure modes that are generally considered "bad". Band-aid over this with a semaphore to limit the number of tasks in the queue. The semaphore is opt-in and disabled by default to avoid any nasty surprises for folks upgrading. Also add 'EventSource.awaitClosed()' to allow users to wait for underlying thread pools to completely shut down. We can't know if it's safe to clean up resources used by the EventHandler thread if we can't be certain that it has completely terminated. * Address checkstyle griping in StubServer * Fix JavaDoc issue * Tighten up exception handling Co-authored-by: Eli Bishop <[email protected]> * update @SInCE * test Java 17 in CI (#51) * improve tests for AsyncEventHandler and EventSource.awaitClosed (#52) * add streaming data mode for very large events (#53) * add option to ensure that expected fields are always read * add Gradle option to suppress kotlin-stdlib in our pom * update okhttp to 4.9.3 * use LaunchDarkly logging facade * rm unused * misc fixes * improve javadoc links * consistently use placeholders instead of concatenation in log output Co-authored-by: Eli Bishop <[email protected]> Co-authored-by: LaunchDarklyCI <[email protected]> Co-authored-by: LaunchDarklyCI <[email protected]> Co-authored-by: Gavin Whelan <[email protected]> Co-authored-by: LaunchDarklyReleaseBot <[email protected]> Co-authored-by: Tom Lee <[email protected]>
1 parent 3b4d49b commit 44ebdb5

19 files changed

+303
-264
lines changed

build.gradle.kts

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import java.time.Duration
22
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
33
import org.gradle.external.javadoc.CoreJavadocOptions
4+
import org.gradle.external.javadoc.StandardJavadocDocletOptions
45

56
// These values come from gradle.properties
67
val ossrhUsername: String by project
@@ -60,14 +61,18 @@ java {
6061
}
6162

6263
object Versions {
64+
const val launchdarklyLogging = "1.1.0"
6365
const val okhttp = "4.9.3"
6466
const val slf4j = "1.7.22"
6567
}
6668

6769
dependencies {
70+
api("com.launchdarkly:launchdarkly-logging:${Versions.launchdarklyLogging}")
6871
api("com.squareup.okhttp3:okhttp:${Versions.okhttp}")
6972
api("org.slf4j:slf4j-api:${Versions.slf4j}")
70-
testImplementation("ch.qos.logback:logback-classic:1.1.9")
73+
// SLF4J is no longer referenced directly by okhttp-eventsource, but since the default behavior is
74+
// to use the SLF4J adapter from com.launchdarkly.logging, we are still retaining the dependency
75+
// here to make sure it is in the classpath.
7176
testImplementation("org.mockito:mockito-core:1.10.19")
7277
testImplementation("com.launchdarkly:test-helpers:1.0.0")
7378
testImplementation("com.google.guava:guava:30.1-jre")
@@ -90,6 +95,14 @@ tasks.javadoc.configure {
9095
// See JDK-8200363 (https://bugs.openjdk.java.net/browse/JDK-8200363)
9196
// for information about the -Xwerror option.
9297
(options as CoreJavadocOptions).addStringOption("Xwerror")
98+
99+
// The following should allow hyperlinks to com.launchdarkly.logging classes to go to
100+
// the correct external URLs
101+
if (options is StandardJavadocDocletOptions) {
102+
(options as StandardJavadocDocletOptions).links(
103+
"https://javadoc.io/doc/com.launchdarkly/launchdarkly-logging/${Versions.launchdarklyLogging}"
104+
)
105+
}
93106
}
94107

95108
tasks.test.configure {
@@ -122,6 +135,11 @@ tasks.jacocoTestCoverageVerification.configure {
122135
"EventSource.run()" to 3,
123136
"EventSource.Builder.createInitialClientBuilder()" to 1,
124137
"EventSource.Builder.defaultTrustManager()" to 2,
138+
"EventSource.Builder.loggerBaseName(java.lang.String)" to 2,
139+
"LoggerBridge.ChannelImpl.log(com.launchdarkly.logging.LDLogLevel, java.lang.String, java.lang.Object[])" to 7,
140+
"LoggerBridge.ChannelImpl.log(com.launchdarkly.logging.LDLogLevel, java.lang.String, java.lang.Object)" to 7,
141+
"LoggerBridge.ChannelImpl.log(com.launchdarkly.logging.LDLogLevel, java.lang.Object)" to 7,
142+
"LoggerBridge.ChannelImpl.isEnabled(com.launchdarkly.logging.LDLogLevel)" to 1,
125143
"MessageEvent.getData()" to 2,
126144
"SLF4JLogger.error(java.lang.String)" to 2,
127145
"ModernTLSSocketFactory.createSocket(java.lang.String, int)" to 1,

contract-tests/service/build.gradle

-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ application {
2222

2323
ext.versions = [
2424
"gson": "2.7",
25-
"logback": "1.1.3",
2625
"testHelpers": "1.1.0"
2726
]
2827

@@ -32,7 +31,6 @@ configurations {
3231

3332
dependencies {
3433
implementation project(":eventsource")
35-
implementation "ch.qos.logback:logback-classic:${versions.logback}"
3634
implementation "com.google.code.gson:gson:${versions.gson}"
3735
implementation "com.launchdarkly:test-helpers:${versions.testHelpers}"
3836
}

contract-tests/service/src/main/java/ssetest/StreamEntity.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package ssetest;
22

33
import com.launchdarkly.eventsource.*;
4-
import org.slf4j.Logger;
5-
import org.slf4j.LoggerFactory;
4+
import com.launchdarkly.logging.*;
65
import java.net.URI;
76
import java.time.Duration;
87
import java.util.concurrent.atomic.AtomicInteger;
@@ -16,19 +15,19 @@ public class StreamEntity implements EventHandler {
1615
private final EventSource stream;
1716
private final StreamOptions options;
1817
private final AtomicInteger callbackMessageCounter = new AtomicInteger(0);
19-
private final Logger logger;
18+
private final LDLogger logger;
2019
private volatile boolean closed;
2120

22-
public StreamEntity(TestService owner, String id, StreamOptions options) {
21+
public StreamEntity(TestService owner, String id, StreamOptions options, LDLogAdapter logAdapter) {
2322
this.owner = owner;
2423
this.id = id;
2524
this.options = options;
2625

27-
this.logger = LoggerFactory.getLogger(options.tag);
26+
this.logger = LDLogger.withAdapter(logAdapter, options.tag);
2827
logger.info("Opening stream to {}", options.streamUrl);
2928

3029
EventSource.Builder eb = new EventSource.Builder(this, URI.create(options.streamUrl))
31-
.loggerBaseName(options.tag + ".stream");
30+
.logger(logger.subLogger("stream"));
3231
if (options.headers != null) {
3332
Headers.Builder hb = new Headers.Builder();
3433
for (String name: options.headers.keySet()) {

contract-tests/service/src/main/java/ssetest/TestService.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ssetest;
22

33
import com.google.gson.Gson;
4+
import com.launchdarkly.logging.*;
45
import com.launchdarkly.testhelpers.httptest.*;
56
import java.util.*;
67
import java.util.concurrent.ConcurrentHashMap;
@@ -15,14 +16,12 @@ public class TestService {
1516

1617
final Gson gson = new Gson();
1718
final OkHttpClient client = new OkHttpClient();
19+
final LDLogAdapter logAdapter = Logs.toStream(System.out);
1820

1921
private final Map<String, StreamEntity> streams = new ConcurrentHashMap<String, StreamEntity>();
2022
private final AtomicInteger streamCounter = new AtomicInteger(0);
2123

2224
public static void main(String[] args) {
23-
// ((ch.qos.logback.classic.Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME)).setLevel(
24-
// Level.valueOf(config.logLevel.toUpperCase()));
25-
2625
TestService service = new TestService();
2726

2827
SimpleRouter router = new SimpleRouter()
@@ -61,7 +60,7 @@ private void postCreateStream(RequestContext ctx) {
6160
StreamOptions opts = readJson(ctx, StreamOptions.class);
6261

6362
String streamId = String.valueOf(streamCounter.incrementAndGet());
64-
StreamEntity stream = new StreamEntity(this, streamId, opts);
63+
StreamEntity stream = new StreamEntity(this, streamId, opts, logAdapter);
6564

6665
streams.put(streamId, stream);
6766

contract-tests/service/src/main/resources/logback.xml

-18
This file was deleted.

src/main/java/com/launchdarkly/eventsource/AsyncEventHandler.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.launchdarkly.eventsource;
22

33

4+
import com.launchdarkly.logging.LDLogger;
5+
46
import java.util.concurrent.Executor;
57
import java.util.concurrent.RejectedExecutionException;
68
import java.util.concurrent.Semaphore;
@@ -16,10 +18,10 @@
1618
final class AsyncEventHandler implements EventHandler {
1719
private final Executor executor;
1820
private final EventHandler eventSourceHandler;
19-
private final Logger logger;
21+
private final LDLogger logger;
2022
final Semaphore semaphore; // visible for tests
2123

22-
AsyncEventHandler(Executor executor, EventHandler eventSourceHandler, Logger logger, Semaphore semaphore) {
24+
AsyncEventHandler(Executor executor, EventHandler eventSourceHandler, LDLogger logger, Semaphore semaphore) {
2325
this.executor = executor;
2426
this.eventSourceHandler = eventSourceHandler;
2527
this.logger = logger;

src/main/java/com/launchdarkly/eventsource/EventParser.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.launchdarkly.eventsource;
22

3+
import com.launchdarkly.logging.LDLogger;
4+
35
import java.io.ByteArrayOutputStream;
46
import java.io.IOException;
57
import java.io.InputStream;
@@ -34,7 +36,7 @@ final class EventParser {
3436
private final ConnectionHandler connectionHandler;
3537
private final boolean streamEventData;
3638
private Set<String> expectFields;
37-
private final Logger logger;
39+
private final LDLogger logger;
3840
private final URI origin;
3941

4042
private BufferedLineParser lineParser;
@@ -59,7 +61,7 @@ final class EventParser {
5961
int readBufferSize,
6062
boolean streamEventData,
6163
Set<String> expectFields,
62-
Logger logger
64+
LDLogger logger
6365
) {
6466
this.lineParser = new BufferedLineParser(inputStream,
6567
readBufferSize < MIN_READ_BUFFER_SIZE ? MIN_READ_BUFFER_SIZE : readBufferSize);

src/main/java/com/launchdarkly/eventsource/EventSource.java

+62-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.launchdarkly.eventsource;
22

3+
import com.launchdarkly.logging.LDLogger;
4+
import com.launchdarkly.logging.LDSLF4J;
5+
36
import java.io.Closeable;
47
import java.io.EOFException;
58
import java.io.IOException;
@@ -63,7 +66,7 @@
6366
* which allows for greater efficiency in some use cases but has some behavioral constraints.
6467
*/
6568
public class EventSource implements Closeable {
66-
final Logger logger; // visible for tests
69+
final LDLogger logger; // visible for tests
6770

6871
/**
6972
* The default value for {@link Builder#reconnectTime(Duration)}: 1 second.
@@ -124,7 +127,7 @@ public class EventSource implements Closeable {
124127
if (builder.logger == null) {
125128
String loggerName = (builder.loggerBaseName == null ? EventSource.class.getCanonicalName() : builder.loggerBaseName) +
126129
(name.isEmpty() ? "" : ("." + name));
127-
this.logger = new SLF4JLogger(loggerName);
130+
this.logger = LDLogger.withAdapter(LDSLF4J.adapter(), loggerName);
128131
} else {
129132
this.logger = builder.logger;
130133
}
@@ -182,7 +185,7 @@ public void start() {
182185
return;
183186
}
184187
logger.debug("readyState change: {} -> {}", RAW, CONNECTING);
185-
logger.info("Starting EventSource client using URI: " + url);
188+
logger.info("Starting EventSource client using URI: {}", url);
186189
streamExecutor.execute(this::run);
187190
}
188191

@@ -282,7 +285,7 @@ private void closeCurrentStream(ReadyState previousState) {
282285
// Otherwise, an IllegalArgumentException "Unbalanced enter/exit" error is thrown by okhttp.
283286
// https://github.com/google/ExoPlayer/issues/1348
284287
call.cancel();
285-
logger.debug("call cancelled", null);
288+
logger.debug("call cancelled");
286289
}
287290
}
288291

@@ -337,7 +340,7 @@ private int maybeReconnectDelay(int reconnectAttempts, long connectedTime) {
337340

338341
try {
339342
Duration sleepTime = backoffWithJitter(counter);
340-
logger.info("Waiting " + sleepTime.toMillis() + " milliseconds before reconnecting...");
343+
logger.info("Waiting {} milliseconds before reconnecting...", sleepTime.toMillis());
341344
Thread.sleep(sleepTime.toMillis());
342345
} catch (InterruptedException ignored) { // COVERAGE: no way to cause this in unit tests
343346
}
@@ -575,7 +578,7 @@ public static final class Builder {
575578
private RequestBody body = null;
576579
private OkHttpClient.Builder clientBuilder;
577580
private int readBufferSize = DEFAULT_READ_BUFFER_SIZE;
578-
private Logger logger = null;
581+
private LDLogger logger = null;
579582
private String loggerBaseName = null;
580583
private int maxEventTasksInFlight = 0;
581584
private boolean streamEventData;
@@ -667,11 +670,11 @@ public Builder requestTransformer(RequestTransformer requestTransformer) {
667670
}
668671

669672
/**
670-
* Set the name for this EventSource client to be used when naming the logger and threadpools. This is mainly useful when
671-
* multiple EventSource clients exist within the same process.
673+
* Set the name for this EventSource client to be used when naming thread pools (and, possibly, the logger).
674+
* This is mainly useful when multiple EventSource clients exist within the same process.
672675
* <p>
673676
* The name only affects logging when using the default SLF4J integration; if you have specified a custom
674-
* {@link #logger(Logger)}, the name will not be included in log messages unless your logger implementation adds it.
677+
* {@link #logger(LDLogger)}, the logging facade has its own way to specify a logger name.
675678
*
676679
* @param name the name (without any whitespaces)
677680
* @return the builder
@@ -925,13 +928,51 @@ public Builder readBufferSize(int readBufferSize) {
925928
/**
926929
* Specifies a custom logger to receive EventSource logging.
927930
* <p>
931+
* This has been superseded by {@link #logger(LDLogger)}. The
932+
* <a href="https://github.com/launchdarkly/java-logging">com.launchdarkly.logging</a>
933+
* facade used by that method provides many options for customizing logging behavior.
934+
* The {@link Logger} interface defined by {@code okhttp-eventsource} will be removed
935+
* in a future major version release.
936+
* <p>
928937
* If you do not provide a logger, the default is to send log output to SLF4J.
929938
*
930939
* @param logger a {@link Logger} implementation, or null to use the default (SLF4J)
931940
* @return the builder
932941
* @since 2.3.0
942+
* @deprecated use {@link #logger(LDLogger)}
933943
*/
944+
@Deprecated
934945
public Builder logger(Logger logger) {
946+
this.logger = logger == null ? null : LoggerBridge.wrapLogger(logger);
947+
return this;
948+
}
949+
950+
/**
951+
* Specifies a custom logger to receive EventSource logging.
952+
* <p>
953+
* This method uses the {@link LDLogger} type from
954+
* <a href="https://github.com/launchdarkly/java-logging">com.launchdarkly.logging</a>, a
955+
* facade that provides several logging implementations as well as the option to forward
956+
* log output to SLF4J or another framework. Here is an example of configuring it to use
957+
* the basic console logging implementation, and to tag the output with the name "logname":
958+
* <pre><code>
959+
* // import com.launchdarkly.logging.*;
960+
*
961+
* builder.logger(
962+
* LDLogger.withAdapter(Logs.basic(), "logname")
963+
* );
964+
* </code></pre>
965+
* <p>
966+
* If you do not provide a logger, the default is to send log output to SLF4J, and to use
967+
* a logger name based on the {@link #loggerBaseName(String)} and {@link #name(String)}
968+
* settings. In a future major version, the default behavior may be changed so that this
969+
* library no longer has a mandatory dependency on SLF4J.
970+
*
971+
* @param logger an {@link LDLogger} implementation, or null to use the default (SLF4J)
972+
* @return the builder
973+
* @since 2.7.0
974+
*/
975+
public Builder logger(LDLogger logger) {
935976
this.logger = logger;
936977
return this;
937978
}
@@ -942,11 +983,23 @@ public Builder logger(Logger logger) {
942983
* The default is {@code com.launchdarkly.eventsource.EventSource}, plus any name suffix specified
943984
* by {@link #name(String)}. If you instead use {@link #logger(Logger)} to specify some other log
944985
* destination rather than SLF4J, this name is unused.
986+
* <p>
987+
* This method is now deprecated, because the logging facade used by {@link #logger(LDLogger)}
988+
* makes it easy to set a logger name for SLF4J, as in this example:
989+
* <pre><code>
990+
* // import com.launchdarkly.logging.*;
991+
*
992+
* builder.logger(
993+
* LDLogger.withAdapter(LDSLF4J.adapter(), "my.preferred.log.name")
994+
* );
995+
* </code></pre>
945996
*
946997
* @param loggerBaseName the SLF4J logger name, or null to use the default
947998
* @return the builder
948999
* @since 2.3.0
1000+
* @deprecated use {@link #logger(LDLogger)}
9491001
*/
1002+
@Deprecated
9501003
public Builder loggerBaseName(String loggerBaseName) {
9511004
this.loggerBaseName = loggerBaseName;
9521005
return this;

src/main/java/com/launchdarkly/eventsource/Logger.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package com.launchdarkly.eventsource;
22

33
/**
4-
* Interface for a custom logger that an application can provide to receive EventSource logging.
4+
* Deprecated interface for a custom logger that an application can provide to receive EventSource logging.
5+
* <p>
6+
* This has been superseded by {@link EventSource.Builder#logger(com.launchdarkly.logging.LDLogger)},
7+
* which uses the <a href="https://github.com/launchdarkly/java-logging">com.launchdarkly.logging</a>
8+
* facade, providing many options for customizing logging behavior. The {@link Logger} interface
9+
* defined by {@code okhttp-eventsource} will be removed in a future major version release
510
* <p>
611
* If you do not provide a logger, the default is to send log output to SLF4J.
712
*
813
* @since 2.3.0
14+
* @deprecated use {@link EventSource.Builder#logger(com.launchdarkly.logging.LDLogger)}
915
*/
16+
@Deprecated
1017
public interface Logger {
1118
/**
1219
* Logs a debug message. Debug output includes verbose details that applications will normally

0 commit comments

Comments
 (0)