22
33import com .google .common .collect .ImmutableList ;
44import com .launchdarkly .logging .LDLogger ;
5+ import com .launchdarkly .logging .LogValues ;
6+ import com .launchdarkly .sdk .fdv2 .SourceSignal ;
57import com .launchdarkly .sdk .server .datasources .FDv2SourceResult ;
68import com .launchdarkly .sdk .server .datasources .Initializer ;
79import com .launchdarkly .sdk .server .datasources .Synchronizer ;
@@ -48,6 +50,13 @@ class FDv2DataSource implements DataSource {
4850
4951 private volatile boolean closed = false ;
5052
53+ /**
54+ * Avoid duplicate orchestration logs for the same synchronizer and {@link SourceSignal} (DATASYSTEM 1.6.6).
55+ */
56+ private String lastLoggedSynchronizerDedupeName ;
57+
58+ private SourceSignal lastLoggedSynchronizerDedupeStatus ;
59+
5160 public interface DataSourceFactory <T > {
5261 T build ();
5362 }
@@ -73,7 +82,6 @@ public FDv2DataSource(
7382 );
7483 }
7584
76-
7785 public FDv2DataSource (
7886 ImmutableList <DataSourceFactory <Initializer >> initializers ,
7987 ImmutableList <DataSourceFactory <Synchronizer >> synchronizers ,
@@ -117,7 +125,9 @@ private void run() {
117125 if (!sourceManager .hasAvailableSources ()) {
118126 // There are not any initializer or synchronizers, so we are at the best state that
119127 // can be achieved.
120- logger .info ("LaunchDarkly client will not connect to Launchdarkly for feature flag data due to no initializers or synchronizers" );
128+ logger .warn (
129+ "LaunchDarkly client will not connect to LaunchDarkly for feature flag data due to no initializers or synchronizers configured."
130+ );
121131 dataSourceUpdates .updateStatus (DataSourceStatusProvider .State .VALID , null );
122132 startFuture .complete (true );
123133 return ;
@@ -157,13 +167,16 @@ private void run() {
157167 private void runInitializers () {
158168 boolean anyDataReceived = false ;
159169 Initializer initializer = sourceManager .getNextInitializerAndSetActive ();
160- while (initializer != null ) {
170+ while (initializer != null ) {
171+ String initializerName = initializer .name ();
172+ logger .info ("Initializer '{}' is starting." , initializerName );
161173 try {
162- try (FDv2SourceResult result = initializer .run ().get ()) {
174+ try (FDv2SourceResult result = initializer .run ().get ()) {
163175 switch (result .getResultType ()) {
164176 case CHANGE_SET :
165177 dataSourceUpdates .apply (result .getChangeSet ());
166178 anyDataReceived = true ;
179+ logger .info ("Initialized via '{}'." , initializerName );
167180 if (!result .getChangeSet ().getSelector ().isEmpty ()) {
168181 // We received data with a selector, so we end the initialization process.
169182 dataSourceUpdates .updateStatus (DataSourceStatusProvider .State .VALID , null );
@@ -176,6 +189,9 @@ private void runInitializers() {
176189 switch (status .getState ()) {
177190 case INTERRUPTED :
178191 case TERMINAL_ERROR :
192+ logger .warn ("Initializer '{}' failed: {}" ,
193+ initializerName ,
194+ detailForError (status .getErrorInfo ()));
179195 // The data source updates handler will filter the state during initializing, but this
180196 // will make the error information available.
181197 dataSourceUpdates .updateStatus (
@@ -194,8 +210,6 @@ private void runInitializers() {
194210 }
195211 }
196212 } catch (ExecutionException | InterruptedException | CancellationException e ) {
197- // We don't expect these conditions to happen in practice.
198-
199213 // The data source updates handler will filter the state during initializing, but this
200214 // will make the error information available.
201215 dataSourceUpdates .updateStatus (
@@ -204,9 +218,12 @@ private void runInitializers() {
204218 0 ,
205219 e .toString (),
206220 new Date ().toInstant ()));
207- logger .warn ("Error running initializer: {}" , e .toString ());
221+ Throwable root = e instanceof ExecutionException && e .getCause () != null ? e .getCause () : e ;
222+ logger .error ("Error running initializer '{}': {}" ,
223+ initializerName ,
224+ root .getMessage () != null ? root .getMessage () : LogValues .exceptionSummary (root ));
208225 }
209- initializer = sourceManager .getNextInitializerAndSetActive ();
226+ initializer = sourceManager .getNextInitializerAndSetActive ();
210227 }
211228 // We received data without a selector, and we have exhausted initializers, so we are going to
212229 // consider ourselves initialized.
@@ -249,6 +266,9 @@ private void runSynchronizers() {
249266
250267 // We want to continue running synchronizers for as long as any are available.
251268 while (synchronizer != null ) {
269+ String synchronizerName = synchronizer .name ();
270+ logger .info ("Synchronizer '{}' is starting." , synchronizerName );
271+ resetSynchronizerStatusDedupe ();
252272 try {
253273 boolean running = true ;
254274
@@ -264,16 +284,19 @@ private void runSynchronizers() {
264284 Condition c = (Condition ) res ;
265285 switch (c .getType ()) {
266286 case FALLBACK :
267- // For fallback, we will move to the next available synchronizer, which may loop.
268- // This is the default behavior of exiting the run loop, so we don't need to take
269- // any action.
270- logger . debug ( "A synchronizer has experienced an interruption and we are falling back." );
287+ logger . info (
288+ "Fallback condition met, falling back from synchronizer '{}'." ,
289+ synchronizer . name ()
290+ );
271291 break ;
272292 case RECOVERY :
273293 // For recovery, we will start at the first available synchronizer.
274294 // So we reset the source index, and finding the source will start at the beginning.
275295 sourceManager .resetSourceIndex ();
276- logger .debug ("The data source is attempting to recover to a higher priority synchronizer." );
296+ logger .info (
297+ "Recovery condition met, moving from synchronizer '{}' to primary synchronizer." ,
298+ synchronizer .name ()
299+ );
277300 break ;
278301 }
279302 // A running synchronizer will only have fallback and recovery conditions that it can act on.
@@ -292,6 +315,8 @@ private void runSynchronizers() {
292315
293316 switch (result .getResultType ()) {
294317 case CHANGE_SET :
318+ // A data update breaks the "in a row" streak for status deduplication (DATASYSTEM 1.6.6).
319+ resetSynchronizerStatusDedupe ();
295320 dataSourceUpdates .apply (result .getChangeSet ());
296321 dataSourceUpdates .updateStatus (DataSourceStatusProvider .State .VALID , null );
297322 // This could have been completed by any data source. But if it has not been completed before
@@ -302,17 +327,33 @@ private void runSynchronizers() {
302327 FDv2SourceResult .Status status = result .getStatus ();
303328 switch (status .getState ()) {
304329 case INTERRUPTED :
330+ maybeLogSynchronizerStatusChange (
331+ synchronizer .name (),
332+ status .getState ()
333+ );
305334 // Handled by conditions.
306335 dataSourceUpdates .updateStatus (
307336 DataSourceStatusProvider .State .INTERRUPTED ,
308337 status .getErrorInfo ());
309338 break ;
310339 case SHUTDOWN :
340+ maybeLogSynchronizerStatusChange (
341+ synchronizer .name (),
342+ status .getState ()
343+ );
311344 // We should be overall shutting down.
312345 logger .debug ("Synchronizer shutdown." );
313346 return ;
314347 case TERMINAL_ERROR :
348+ maybeLogSynchronizerStatusChange (
349+ synchronizer .name (),
350+ status .getState ()
351+ );
315352 sourceManager .blockCurrentSynchronizer ();
353+ logger .warn (
354+ "Synchronizer '{}' permanently failed and will not be used again until application restart." ,
355+ synchronizer .name ()
356+ );
316357 running = false ;
317358 dataSourceUpdates .updateStatus (
318359 DataSourceStatusProvider .State .INTERRUPTED ,
@@ -335,6 +376,7 @@ private void runSynchronizers() {
335376 !sourceManager .isCurrentSynchronizerFDv1Fallback ()
336377 ) {
337378 sourceManager .fdv1Fallback ();
379+ logger .info ("Falling back to an FDv1 fallback synchronizer." );
338380 running = false ;
339381 }
340382 }
@@ -348,20 +390,55 @@ private void runSynchronizers() {
348390 e .toString (),
349391 new Date ().toInstant ()
350392 ));
351- logger .warn ("Error running synchronizer: {}, will try next synchronizer, or retry." , e .toString ());
393+ Throwable root = e instanceof ExecutionException && e .getCause () != null ? e .getCause () : e ;
394+ logger .error ("Error running synchronizer '{}': {}" ,
395+ synchronizer .name (),
396+ root .getMessage () != null ? root .getMessage () : LogValues .exceptionSummary (root ));
352397 // Move to the next synchronizer.
353398 }
354399 // Get the next available synchronizer and set it active
355400 synchronizer = sourceManager .getNextAvailableSynchronizerAndSetActive ();
356401 }
357- } catch (Exception e ) {
402+ if (!closed ) {
403+ logger .warn ("No more synchronizers available." );
404+ }
405+ } catch (Exception e ) {
358406 // We are not expecting to encounter this situation, but if we do, then we should log it.
359407 logger .error ("Unexpected error in data source: {}" , e .toString ());
360- }finally {
408+ } finally {
361409 sourceManager .close ();
362410 }
363411 }
364412
413+ private static String detailForError (DataSourceStatusProvider .ErrorInfo errorInfo ) {
414+ if (errorInfo == null ) {
415+ return "unknown error" ;
416+ }
417+ if (errorInfo .getMessage () != null && !errorInfo .getMessage ().isEmpty ()) {
418+ return errorInfo .getMessage ();
419+ }
420+ return errorInfo .toString ();
421+ }
422+
423+ private void resetSynchronizerStatusDedupe () {
424+ lastLoggedSynchronizerDedupeName = null ;
425+ lastLoggedSynchronizerDedupeStatus = null ;
426+ }
427+
428+ private void maybeLogSynchronizerStatusChange (String sourceName , SourceSignal state ) {
429+ if (state == SourceSignal .GOODBYE ) {
430+ return ;
431+ }
432+ if (sourceName != null
433+ && sourceName .equals (lastLoggedSynchronizerDedupeName )
434+ && state == lastLoggedSynchronizerDedupeStatus ) {
435+ return ;
436+ }
437+ lastLoggedSynchronizerDedupeName = sourceName ;
438+ lastLoggedSynchronizerDedupeStatus = state ;
439+ logger .info ("Synchronizer '{}' reported status: {}." , sourceName , state .name ());
440+ }
441+
365442 @ Override
366443 public Future <Void > start () {
367444 if (!started .getAndSet (true )) {
0 commit comments