11/*
2- * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved.
2+ * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1717package reactor .util ;
1818
1919import java .io .PrintStream ;
20- import java .util .HashMap ;
20+ import java .lang .ref .WeakReference ;
21+ import java .util .Map ;
22+ import java .util .Objects ;
23+ import java .util .WeakHashMap ;
2124import java .util .function .Function ;
2225import java .util .logging .Level ;
2326import java .util .regex .Matcher ;
@@ -129,6 +132,12 @@ public static void useVerboseConsoleLoggers() {
129132 * The previously active logger factory is simply replaced without
130133 * any particular clean-up.
131134 *
135+ * <h4>Thread-safety</h4>
136+ *
137+ * Given logger acquisition function <em>must</em> be thread-safe.
138+ * It means that it is user responsibility to ensure that any internal state and cache
139+ * used by the provided function is properly synchronized.
140+ *
132141 * @param loggerFactory the {@link Function} that provides a (possibly cached) {@link Logger}
133142 * given a name.
134143 */
@@ -461,25 +470,27 @@ public Logger apply(String name) {
461470 */
462471 static final class ConsoleLogger implements Logger {
463472
464- private final String name ;
473+ private final ConsoleLoggerKey identifier ;
465474 private final PrintStream err ;
466475 private final PrintStream log ;
467- private final boolean verbose ;
468476
469- ConsoleLogger (String name , PrintStream log , PrintStream err , boolean verbose ) {
470- this .name = name ;
477+ ConsoleLogger (ConsoleLoggerKey identifier , PrintStream log , PrintStream err ) {
478+ this .identifier = identifier ;
471479 this .log = log ;
472480 this .err = err ;
473- this .verbose = verbose ;
474481 }
475482
476- ConsoleLogger (String name , boolean verbose ) {
477- this (name , System .out , System .err , verbose );
483+ ConsoleLogger (String name , PrintStream log , PrintStream err , boolean verbose ) {
484+ this (new ConsoleLoggerKey (name , verbose ), log , err );
485+ }
486+
487+ ConsoleLogger (ConsoleLoggerKey identifier ) {
488+ this (identifier , System .out , System .err );
478489 }
479490
480491 @ Override
481492 public String getName () {
482- return this .name ;
493+ return identifier .name ;
483494 }
484495
485496 @ Nullable
@@ -498,27 +509,27 @@ final String format(@Nullable String from, @Nullable Object... arguments){
498509
499510 @ Override
500511 public boolean isTraceEnabled () {
501- return verbose ;
512+ return identifier . verbose ;
502513 }
503514
504515 @ Override
505516 public synchronized void trace (String msg ) {
506- if (!verbose ) {
517+ if (!identifier . verbose ) {
507518 return ;
508519 }
509520 this .log .format ("[TRACE] (%s) %s\n " , Thread .currentThread ().getName (), msg );
510521 }
511522
512523 @ Override
513524 public synchronized void trace (String format , Object ... arguments ) {
514- if (!verbose ) {
525+ if (!identifier . verbose ) {
515526 return ;
516527 }
517528 this .log .format ("[TRACE] (%s) %s\n " , Thread .currentThread ().getName (), format (format , arguments ));
518529 }
519530 @ Override
520531 public synchronized void trace (String msg , Throwable t ) {
521- if (!verbose ) {
532+ if (!identifier . verbose ) {
522533 return ;
523534 }
524535 this .log .format ("[TRACE] (%s) %s - %s\n " , Thread .currentThread ().getName (), msg , t );
@@ -527,28 +538,28 @@ public synchronized void trace(String msg, Throwable t) {
527538
528539 @ Override
529540 public boolean isDebugEnabled () {
530- return verbose ;
541+ return identifier . verbose ;
531542 }
532543
533544 @ Override
534545 public synchronized void debug (String msg ) {
535- if (!verbose ) {
546+ if (!identifier . verbose ) {
536547 return ;
537548 }
538549 this .log .format ("[DEBUG] (%s) %s\n " , Thread .currentThread ().getName (), msg );
539550 }
540551
541552 @ Override
542553 public synchronized void debug (String format , Object ... arguments ) {
543- if (!verbose ) {
554+ if (!identifier . verbose ) {
544555 return ;
545556 }
546557 this .log .format ("[DEBUG] (%s) %s\n " , Thread .currentThread ().getName (), format (format , arguments ));
547558 }
548559
549560 @ Override
550561 public synchronized void debug (String msg , Throwable t ) {
551- if (!verbose ) {
562+ if (!identifier . verbose ) {
552563 return ;
553564 }
554565 this .log .format ("[DEBUG] (%s) %s - %s\n " , Thread .currentThread ().getName (), msg , t );
@@ -617,21 +628,76 @@ public synchronized void error(String msg, Throwable t) {
617628 this .err .format ("[ERROR] (%s) %s - %s\n " , Thread .currentThread ().getName (), msg , t );
618629 t .printStackTrace (this .err );
619630 }
631+
632+ @ Override
633+ public String toString () {
634+ return "ConsoleLogger[name=" +getName ()+", verbose=" +identifier .verbose +"]" ;
635+ }
620636 }
621637
622- private static final class ConsoleLoggerFactory implements Function <String , Logger > {
638+ /**
639+ * A key object to serve a dual purpose:
640+ * <ul>
641+ * <li>Allow consistent identification of cached console loggers using not
642+ * only its name, but also its verbosity level</li>
643+ * <li>Provide an object eligible to cache eviction. Contrary to a logger or
644+ * a string (logger name) object, this is a good candidate for weak reference key,
645+ * because it should be held only internally by the attached logger and by the
646+ * logger cache (as evictable key).</li>
647+ * </ul>
648+ */
649+ private static final class ConsoleLoggerKey {
623650
624- private static final HashMap <String , Logger > consoleLoggers = new HashMap <>();
651+ private final String name ;
652+ private final boolean verbose ;
653+
654+ private ConsoleLoggerKey (String name , boolean verbose ) {
655+ this .name = name ;
656+ this .verbose = verbose ;
657+ }
658+
659+ @ Override
660+ public boolean equals (Object o ) {
661+ if (this == o ) {
662+ return true ;
663+ }
664+ if (o == null || getClass () != o .getClass ()) {
665+ return false ;
666+ }
667+ ConsoleLoggerKey key = (ConsoleLoggerKey ) o ;
668+ return verbose == key .verbose && Objects .equals (name , key .name );
669+ }
670+
671+ @ Override
672+ public int hashCode () {
673+ return Objects .hash (name , verbose );
674+ }
675+ }
676+
677+ static final class ConsoleLoggerFactory implements Function <String , Logger > {
678+
679+ private static final Map <ConsoleLoggerKey , WeakReference <Logger >> consoleLoggers =
680+ new WeakHashMap <>();
625681
626682 final boolean verbose ;
627683
628- private ConsoleLoggerFactory (boolean verbose ) {
684+ ConsoleLoggerFactory (boolean verbose ) {
629685 this .verbose = verbose ;
630686 }
631687
632688 @ Override
633689 public Logger apply (String name ) {
634- return consoleLoggers .computeIfAbsent (name , n -> new ConsoleLogger (n , verbose ));
690+ final ConsoleLoggerKey key = new ConsoleLoggerKey (name , verbose );
691+ synchronized (consoleLoggers ) {
692+ final WeakReference <Logger > ref = consoleLoggers .get (key );
693+ Logger cached = ref == null ? null : ref .get ();
694+ if (cached == null ) {
695+ cached = new ConsoleLogger (key );
696+ consoleLoggers .put (key , new WeakReference <>(cached ));
697+ }
698+
699+ return cached ;
700+ }
635701 }
636702 }
637703
0 commit comments