Browse Source

Fix the issue that apollo initialization logs won't print in eager load mode (#3677)

kl 3 năm trước cách đây
mục cha
commit
dc41c93434
21 tập tin đã thay đổi với 1135 bổ sung41 xóa
  1. 2 2
      apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigFile.java
  2. 8 4
      apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultConfig.java
  3. 5 3
      apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProvider.java
  4. 2 2
      apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java
  5. 2 2
      apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigRepository.java
  6. 4 1
      apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java
  7. 25 16
      apollo-core/src/main/java/com/ctrip/framework/apollo/core/MetaDomainConsts.java
  8. 185 0
      apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/DeferredLogCache.java
  9. 464 0
      apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/DeferredLogger.java
  10. 40 0
      apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/DeferredLoggerFactory.java
  11. 3 2
      apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java
  12. 13 7
      apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultServerProvider.java
  13. 69 0
      apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/DeferredLogCacheTest.java
  14. 48 0
      apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/DeferredLoggerStateTest.java
  15. 97 0
      apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/DeferredLoggerTest.java
  16. 43 0
      apollo-core/src/test/java/com/ctrip/framework/test/tools/AloneClassLoader.java
  17. 85 0
      apollo-core/src/test/java/com/ctrip/framework/test/tools/AloneRunner.java
  18. 36 0
      apollo-core/src/test/java/com/ctrip/framework/test/tools/AloneWith.java
  19. 1 1
      apollo-core/src/test/resources/log4j2.xml
  20. 2 0
      apollo-demo/src/main/resources/application.yml
  21. 1 1
      docs/zh/usage/java-sdk-user-guide.md

+ 2 - 2
apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigFile.java

@@ -17,6 +17,7 @@
 package com.ctrip.framework.apollo.internals;
 
 import com.ctrip.framework.apollo.build.ApolloInjector;
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import com.ctrip.framework.apollo.enums.ConfigSourceType;
 import com.ctrip.framework.apollo.util.factory.PropertiesFactory;
 import java.util.List;
@@ -26,7 +27,6 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.ctrip.framework.apollo.ConfigFile;
 import com.ctrip.framework.apollo.ConfigFileChangeListener;
@@ -42,7 +42,7 @@ import com.google.common.collect.Lists;
  * @author Jason Song(song_s@ctrip.com)
  */
 public abstract class AbstractConfigFile implements ConfigFile, RepositoryChangeListener {
-  private static final Logger logger = LoggerFactory.getLogger(AbstractConfigFile.class);
+  private static final Logger logger = DeferredLoggerFactory.getLogger(AbstractConfigFile.class);
   private static ExecutorService m_executorService;
   protected final ConfigRepository m_configRepository;
   protected final String m_namespace;

+ 8 - 4
apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultConfig.java

@@ -16,6 +16,7 @@
  */
 package com.ctrip.framework.apollo.internals;
 
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import com.ctrip.framework.apollo.enums.ConfigSourceType;
 import java.io.IOException;
 import java.io.InputStream;
@@ -29,7 +30,6 @@ import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.ctrip.framework.apollo.core.utils.ClassLoaderUtil;
 import com.ctrip.framework.apollo.enums.PropertyChangeType;
@@ -45,7 +45,8 @@ import com.google.common.util.concurrent.RateLimiter;
  * @author Jason Song(song_s@ctrip.com)
  */
 public class DefaultConfig extends AbstractConfig implements RepositoryChangeListener {
-  private static final Logger logger = LoggerFactory.getLogger(DefaultConfig.class);
+
+  private static final Logger logger = DeferredLoggerFactory.getLogger(DefaultConfig.class);
   private final String m_namespace;
   private final Properties m_resourceProperties;
   private final AtomicReference<Properties> m_configProperties;
@@ -108,7 +109,9 @@ public class DefaultConfig extends AbstractConfig implements RepositoryChangeLis
     }
 
     if (value == null && m_configProperties.get() == null && m_warnLogRateLimiter.tryAcquire()) {
-      logger.warn("Could not load config for namespace {} from Apollo, please check whether the configs are released in Apollo! Return default value now!", m_namespace);
+      logger.warn(
+          "Could not load config for namespace {} from Apollo, please check whether the configs are released in Apollo! Return default value now!",
+          m_namespace);
     }
 
     return value == null ? defaultValue : value;
@@ -152,7 +155,8 @@ public class DefaultConfig extends AbstractConfig implements RepositoryChangeLis
     Properties newConfigProperties = propertiesFactory.getPropertiesInstance();
     newConfigProperties.putAll(newProperties);
 
-    Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);
+    Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties,
+        sourceType);
 
     //check double checked result
     if (actualChanges.isEmpty()) {

+ 5 - 3
apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProvider.java

@@ -19,15 +19,16 @@ package com.ctrip.framework.apollo.internals;
 import com.ctrip.framework.apollo.core.ConfigConsts;
 import com.ctrip.framework.apollo.core.enums.Env;
 import com.ctrip.framework.apollo.core.spi.MetaServerProvider;
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import com.ctrip.framework.foundation.Foundation;
 import com.google.common.base.Strings;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class DefaultMetaServerProvider implements MetaServerProvider {
 
   public static final int ORDER = 0;
-  private static final Logger logger = LoggerFactory.getLogger(DefaultMetaServerProvider.class);
+  private static final Logger logger = DeferredLoggerFactory
+      .getLogger(DefaultMetaServerProvider.class);
 
   private final String metaServerAddress;
 
@@ -52,7 +53,8 @@ public class DefaultMetaServerProvider implements MetaServerProvider {
     }
 
     if (Strings.isNullOrEmpty(metaAddress)) {
-      logger.warn("Could not find meta server address, because it is not available in neither (1) JVM system property 'apollo.meta', (2) OS env variable 'APOLLO_META' (3) property 'apollo.meta' from server.properties nor (4) property 'apollo.meta' from app.properties");
+      logger.warn(
+          "Could not find meta server address, because it is not available in neither (1) JVM system property 'apollo.meta', (2) OS env variable 'APOLLO_META' (3) property 'apollo.meta' from server.properties nor (4) property 'apollo.meta' from app.properties");
     } else {
       metaAddress = metaAddress.trim();
       logger.info("Located meta services from apollo.meta configuration: {}!", metaAddress);

+ 2 - 2
apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java

@@ -16,6 +16,7 @@
  */
 package com.ctrip.framework.apollo.internals;
 
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import com.ctrip.framework.apollo.enums.ConfigSourceType;
 import java.io.File;
 import java.io.FileInputStream;
@@ -29,7 +30,6 @@ import java.nio.file.Paths;
 import java.util.Properties;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.ctrip.framework.apollo.build.ApolloInjector;
 import com.ctrip.framework.apollo.core.ConfigConsts;
@@ -47,7 +47,7 @@ import com.google.common.base.Preconditions;
  */
 public class LocalFileConfigRepository extends AbstractConfigRepository
     implements RepositoryChangeListener {
-  private static final Logger logger = LoggerFactory.getLogger(LocalFileConfigRepository.class);
+  private static final Logger logger = DeferredLoggerFactory.getLogger(LocalFileConfigRepository.class);
   private static final String CONFIG_DIR = "/config-cache";
   private final String m_namespace;
   private File m_baseDir;

+ 2 - 2
apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigRepository.java

@@ -26,6 +26,7 @@ import com.ctrip.framework.apollo.core.schedule.ExponentialSchedulePolicy;
 import com.ctrip.framework.apollo.core.schedule.SchedulePolicy;
 import com.ctrip.framework.apollo.core.signature.Signature;
 import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import com.ctrip.framework.apollo.core.utils.StringUtils;
 import com.ctrip.framework.apollo.enums.ConfigSourceType;
 import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
@@ -55,13 +56,12 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
  */
 public class RemoteConfigRepository extends AbstractConfigRepository {
-  private static final Logger logger = LoggerFactory.getLogger(RemoteConfigRepository.class);
+  private static final Logger logger = DeferredLoggerFactory.getLogger(RemoteConfigRepository.class);
   private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
   private static final Joiner.MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");
   private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper();

+ 4 - 1
apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java

@@ -19,6 +19,7 @@ package com.ctrip.framework.apollo.spring.boot;
 import com.ctrip.framework.apollo.Config;
 import com.ctrip.framework.apollo.ConfigService;
 import com.ctrip.framework.apollo.core.ConfigConsts;
+import com.ctrip.framework.apollo.core.utils.DeferredLogger;
 import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
 import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
 import com.ctrip.framework.apollo.spring.util.SpringInjector;
@@ -107,7 +108,8 @@ public class ApolloApplicationContextInitializer implements
   protected void initialize(ConfigurableEnvironment environment) {
 
     if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
-      //already initialized
+      //already initialized, replay the logs that were printed before the logging system was initialized
+      DeferredLogger.replayTo();
       return;
     }
 
@@ -176,6 +178,7 @@ public class ApolloApplicationContextInitializer implements
     Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);
 
     if (bootstrapEnabled) {
+      DeferredLogger.enable();
       initialize(configurableEnvironment);
     }
 

+ 25 - 16
apollo-core/src/main/java/com/ctrip/framework/apollo/core/MetaDomainConsts.java

@@ -27,8 +27,8 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.ctrip.framework.apollo.core.spi.MetaServerProvider;
 import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
@@ -41,20 +41,22 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
 /**
- * The meta domain will try to load the meta server address from MetaServerProviders, the default ones are:
+ * The meta domain will try to load the meta server address from MetaServerProviders, the default
+ * ones are:
  *
  * <ul>
  * <li>com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider</li>
  * </ul>
- *
+ * <p>
  * If no provider could provide the meta server url, the default meta url will be used(http://apollo.meta).
  * <br />
- *
+ * <p>
  * 3rd party MetaServerProvider could be injected by typical Java Service Loader pattern.
  *
  * @see com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider
  */
 public class MetaDomainConsts {
+
   public static final String DEFAULT_META_URL = "http://apollo.meta";
 
   // env -> meta server address cache
@@ -62,7 +64,7 @@ public class MetaDomainConsts {
   private static volatile List<MetaServerProvider> metaServerProviders = null;
 
   private static final long REFRESH_INTERVAL_IN_SECOND = 60;// 1 min
-  private static final Logger logger = LoggerFactory.getLogger(MetaDomainConsts.class);
+  private static final Logger logger = DeferredLoggerFactory.getLogger(MetaDomainConsts.class);
   // comma separated meta server address -> selected single meta server address cache
   private static final Map<String, String> selectedMetaServerAddressCache = Maps.newConcurrentMap();
   private static final AtomicBoolean periodicRefreshStarted = new AtomicBoolean(false);
@@ -70,7 +72,8 @@ public class MetaDomainConsts {
   private static final Object LOCK = new Object();
 
   /**
-   * Return one meta server address. If multiple meta server addresses are configured, will select one.
+   * Return one meta server address. If multiple meta server addresses are configured, will select
+   * one.
    */
   public static String getDomain(Env env) {
     String metaServerAddress = getMetaServerAddress(env);
@@ -82,7 +85,8 @@ public class MetaDomainConsts {
   }
 
   /**
-   * Return meta server address. If multiple meta server addresses are configured, will return the comma separated string.
+   * Return meta server address. If multiple meta server addresses are configured, will return the
+   * comma separated string.
    */
   public static String getMetaServerAddress(Env env) {
     if (!metaServerAddressCache.containsKey(env)) {
@@ -124,7 +128,8 @@ public class MetaDomainConsts {
   }
 
   private static List<MetaServerProvider> initMetaServerProviders() {
-    Iterator<MetaServerProvider> metaServerProviderIterator = ServiceBootstrap.loadAll(MetaServerProvider.class);
+    Iterator<MetaServerProvider> metaServerProviderIterator = ServiceBootstrap
+        .loadAll(MetaServerProvider.class);
 
     List<MetaServerProvider> metaServerProviders = Lists.newArrayList(metaServerProviderIterator);
 
@@ -142,11 +147,12 @@ public class MetaDomainConsts {
   /**
    * Select one available meta server from the comma separated meta server addresses, e.g.
    * http://1.2.3.4:8080,http://2.3.4.5:8080
-   *
+   * <p>
    * <br />
-   *
-   * In production environment, we still suggest using one single domain like http://config.xxx.com(backed by software
-   * load balancers like nginx) instead of multiple ip addresses
+   * <p>
+   * In production environment, we still suggest using one single domain like
+   * http://config.xxx.com(backed by software load balancers like nginx) instead of multiple ip
+   * addresses
    */
   private static String selectMetaServerAddress(String metaServerAddresses) {
     String metaAddressSelected = selectedMetaServerAddressCache.get(metaServerAddresses);
@@ -165,7 +171,8 @@ public class MetaDomainConsts {
   private static void updateMetaServerAddresses(String metaServerAddresses) {
     logger.debug("Selecting meta server address for: {}", metaServerAddresses);
 
-    Transaction transaction = Tracer.newTransaction("Apollo.MetaService", "refreshMetaServerAddress");
+    Transaction transaction = Tracer
+        .newTransaction("Apollo.MetaService", "refreshMetaServerAddress");
     transaction.addData("Url", metaServerAddresses);
 
     try {
@@ -193,7 +200,8 @@ public class MetaDomainConsts {
       }
 
       if (!serverAvailable) {
-        logger.warn("Could not find available meta server for configured meta server addresses: {}, fallback to: {}",
+        logger.warn(
+            "Could not find available meta server for configured meta server addresses: {}, fallback to: {}",
             metaServerAddresses, selectedMetaServerAddressCache.get(metaServerAddresses));
       }
 
@@ -218,8 +226,9 @@ public class MetaDomainConsts {
             updateMetaServerAddresses(metaServerAddresses);
           }
         } catch (Throwable ex) {
-          logger.warn(String.format("Refreshing meta server address failed, will retry in %d seconds",
-              REFRESH_INTERVAL_IN_SECOND), ex);
+          logger
+              .warn(String.format("Refreshing meta server address failed, will retry in %d seconds",
+                  REFRESH_INTERVAL_IN_SECOND), ex);
         }
       }
     }, REFRESH_INTERVAL_IN_SECOND, REFRESH_INTERVAL_IN_SECOND, TimeUnit.SECONDS);

+ 185 - 0
apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/DeferredLogCache.java

@@ -0,0 +1,185 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.apollo.core.utils;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.slf4j.Logger;
+import org.slf4j.event.Level;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Delayed log printing utility class, used only for logging when Apollo is initialized
+ *
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/11
+ */
+final class DeferredLogCache {
+
+  public static final int MAX_LOG_SIZE = 1000;
+  private static final AtomicInteger LOG_INDEX = new AtomicInteger(0);
+  private static final Cache<Integer, Line> LOG_CACHE = CacheBuilder.newBuilder()
+      .maximumSize(MAX_LOG_SIZE)
+      .expireAfterWrite(1, TimeUnit.MINUTES)
+      .build();
+
+  private DeferredLogCache() {
+  }
+
+
+  public static void debug(Logger logger, String message, Throwable throwable) {
+    add(logger, Level.DEBUG, message, null, throwable);
+  }
+
+  public static void debug(Logger logger, String message, Object... objects) {
+    add(logger, Level.DEBUG, message, objects, null);
+  }
+
+  public static void info(Logger logger, String message, Throwable throwable) {
+    add(logger, Level.INFO, message, null, throwable);
+  }
+
+  public static void info(Logger logger, String message, Object... objects) {
+    add(logger, Level.INFO, message, objects, null);
+  }
+
+  public static void warn(Logger logger, String message, Object... objects) {
+    add(logger, Level.WARN, message, objects, null);
+  }
+
+  public static void warn(Logger logger, String message, Throwable throwable) {
+    add(logger, Level.WARN, message, null, throwable);
+  }
+
+
+  public static void error(Logger logger, String message, Throwable throwable) {
+    add(logger, Level.ERROR, message, null, throwable);
+  }
+
+  public static void error(Logger logger, String message, Object... objects) {
+    add(logger, Level.ERROR, message, objects, null);
+  }
+
+  private static void add(Logger logger, Level level, String message, Object[] objects,
+      Throwable throwable) {
+    Line logLine = new Line(level, message, objects, throwable, logger);
+    LOG_CACHE.put(LOG_INDEX.incrementAndGet(), logLine);
+  }
+
+  static void replayTo() {
+    for (int i = 1; i <= LOG_INDEX.get(); i++) {
+      Line logLine = LOG_CACHE.getIfPresent(i);
+      assert logLine != null;
+      Logger logger = logLine.getLogger();
+      Level level = logLine.getLevel();
+      String message = logLine.getMessage();
+      Object[] objects = logLine.getObjects();
+      Throwable throwable = logLine.getThrowable();
+      logTo(logger, level, message, objects, throwable);
+    }
+    clear();
+  }
+
+  static void clear() {
+    LOG_CACHE.invalidateAll();
+    LOG_INDEX.set(0);
+  }
+
+  static long logSize() {
+    return LOG_CACHE.size();
+  }
+
+  static void logTo(Logger logger, Level level, String message, Object[] objects,
+      Throwable throwable) {
+    switch (level) {
+      case DEBUG:
+        if (throwable != null) {
+          logger.debug(message, throwable);
+        } else {
+          logger.debug(message, objects);
+        }
+        return;
+      case INFO:
+        if (throwable != null) {
+          logger.info(message, throwable);
+        } else {
+          logger.info(message, objects);
+        }
+        return;
+      case WARN:
+        if (throwable != null) {
+          logger.warn(message, throwable);
+        } else {
+          logger.warn(message, objects);
+        }
+        return;
+      case ERROR:
+        if (throwable != null) {
+          logger.error(message, throwable);
+        } else {
+          logger.error(message, objects);
+        }
+        break;
+      default:
+        throw new IllegalStateException("Unexpected value: " + level);
+    }
+  }
+
+  private static class Line {
+
+    private final Level level;
+
+    private final String message;
+
+    private final Object[] objects;
+
+    private final Throwable throwable;
+
+    private final Logger logger;
+
+    Line(Level level, String message, Object[] objects, Throwable throwable, Logger logger) {
+      this.level = level;
+      this.message = message;
+      this.objects = objects;
+      this.throwable = throwable;
+      this.logger = logger;
+    }
+
+    public Object[] getObjects() {
+      return objects;
+    }
+
+    public Logger getLogger() {
+      return logger;
+    }
+
+    Level getLevel() {
+      return this.level;
+    }
+
+    String getMessage() {
+      return this.message;
+    }
+
+    Throwable getThrowable() {
+      return this.throwable;
+    }
+
+  }
+}

+ 464 - 0
apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/DeferredLogger.java

@@ -0,0 +1,464 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.apollo.core.utils;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+/**
+ * Only the following methods implement the delayed log function
+ * <pre class="code">
+ *  public void debug(String s);
+ *  public void debug(String s, Object o);
+ *  public void debug(String s, Object... objects);
+ *  public void debug(String s, Object o, Object o1);
+ *  public void debug(String s, Throwable throwable);
+ *  public void info(String s);
+ *  public void info(String s, Object o);
+ *  public void info(String s, Object... objects);
+ *  public void info(String s, Object o, Object o1);
+ *  public void info(String s, Throwable throwable);
+ *  public void warn(String s);
+ *  public void warn(String s, Object o);
+ *  public void warn(String s, Object... objects);
+ *  public void warn(String s, Object o, Object o1);
+ *  public void warn(String s, Throwable throwable)
+ *  public void error(String s);
+ *  public void error(String s, Object o);
+ *  public void error(String s, Object o, Object o1);
+ *  public void error(String s, Object... objects);
+ *  public void error(String s, Throwable throwable);
+ * </pre>
+ *
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/19
+ */
+public class DeferredLogger implements Logger {
+
+  private final Logger logger;
+  /**
+   * Delay the opening state of the log -1: Uninitialized 0: Disable 1: Enabled
+   */
+  private static final AtomicInteger state = new AtomicInteger(-1);
+
+  public static void enable() {
+    state.compareAndSet(-1, 1);
+  }
+
+  public static void disable() {
+    state.set(0);
+  }
+
+  public static boolean isEnabled() {
+    return state.get() == 1;
+  }
+
+  public static void replayTo() {
+    disable();
+    DeferredLogCache.replayTo();
+  }
+
+  public DeferredLogger(Logger logger) {
+    this.logger = logger;
+  }
+
+  @Override
+  public String getName() {
+    return logger.getName();
+  }
+
+  @Override
+  public boolean isTraceEnabled() {
+    return logger.isTraceEnabled();
+  }
+
+  @Override
+  public void trace(String s) {
+    logger.trace(s);
+  }
+
+  @Override
+  public void trace(String s, Object o) {
+    logger.trace(s, o);
+  }
+
+  @Override
+  public void trace(String s, Object o, Object o1) {
+    logger.trace(s, o, o1);
+  }
+
+  @Override
+  public void trace(String s, Object... objects) {
+    logger.trace(s, objects);
+  }
+
+  @Override
+  public void trace(String s, Throwable throwable) {
+    logger.trace(s, throwable);
+  }
+
+  @Override
+  public boolean isTraceEnabled(Marker marker) {
+    return logger.isTraceEnabled(marker);
+  }
+
+  @Override
+  public void trace(Marker marker, String s) {
+    logger.trace(marker, s);
+  }
+
+  @Override
+  public void trace(Marker marker, String s, Object o) {
+    logger.trace(marker, s, o);
+  }
+
+  @Override
+  public void trace(Marker marker, String s, Object o, Object o1) {
+    logger.trace(marker, s, o, o1);
+  }
+
+  @Override
+  public void trace(Marker marker, String s, Object... objects) {
+    logger.trace(marker, s, objects);
+  }
+
+  @Override
+  public void trace(Marker marker, String s, Throwable throwable) {
+    logger.trace(marker, s, throwable);
+  }
+
+  @Override
+  public boolean isDebugEnabled() {
+    return logger.isDebugEnabled();
+  }
+
+  @Override
+  public void debug(String s) {
+    if (isEnabled()) {
+      DeferredLogCache.debug(logger, s);
+    } else {
+      logger.debug(s);
+    }
+  }
+
+  @Override
+  public void debug(String s, Object o) {
+    if (isEnabled()) {
+      DeferredLogCache.debug(logger, s, o);
+    } else {
+      logger.debug(s, o);
+    }
+  }
+
+  @Override
+  public void debug(String s, Object o, Object o1) {
+    if (isEnabled()) {
+      DeferredLogCache.debug(logger, s, o, o1);
+    } else {
+      logger.debug(s, o, o1);
+    }
+  }
+
+  @Override
+  public void debug(String s, Object... objects) {
+    if (isEnabled()) {
+      DeferredLogCache.debug(logger, s, objects);
+    } else {
+      logger.debug(s, objects);
+    }
+  }
+
+  @Override
+  public void debug(String s, Throwable throwable) {
+    if (isEnabled()) {
+      DeferredLogCache.debug(logger, s, throwable);
+    } else {
+      logger.debug(s, throwable);
+    }
+  }
+
+  @Override
+  public boolean isDebugEnabled(Marker marker) {
+    return logger.isDebugEnabled(marker);
+  }
+
+  @Override
+  public void debug(Marker marker, String s) {
+    logger.debug(marker, s);
+  }
+
+  @Override
+  public void debug(Marker marker, String s, Object o) {
+    logger.debug(marker, s, o);
+  }
+
+  @Override
+  public void debug(Marker marker, String s, Object o, Object o1) {
+    logger.debug(marker, s, o, o1);
+  }
+
+  @Override
+  public void debug(Marker marker, String s, Object... objects) {
+    logger.debug(marker, s, objects);
+  }
+
+  @Override
+  public void debug(Marker marker, String s, Throwable throwable) {
+    logger.debug(marker, s, throwable);
+  }
+
+  @Override
+  public boolean isInfoEnabled() {
+    return logger.isInfoEnabled();
+  }
+
+  @Override
+  public void info(String s) {
+    if (isEnabled()) {
+      DeferredLogCache.info(logger, s);
+    } else {
+      logger.info(s);
+    }
+  }
+
+  @Override
+  public void info(String s, Object o) {
+    if (isEnabled()) {
+      DeferredLogCache.info(logger, s, o);
+    } else {
+      logger.info(s, o);
+    }
+  }
+
+  @Override
+  public void info(String s, Object o, Object o1) {
+    if (isEnabled()) {
+      DeferredLogCache.info(logger, s, o, o1);
+    } else {
+      logger.info(s, o, o1);
+    }
+  }
+
+  @Override
+  public void info(String s, Object... objects) {
+    if (isEnabled()) {
+      DeferredLogCache.info(logger, s, objects);
+    } else {
+      logger.info(s, objects);
+    }
+  }
+
+  @Override
+  public void info(String s, Throwable throwable) {
+    if (isEnabled()) {
+      DeferredLogCache.info(logger, s, throwable);
+    } else {
+      logger.info(s, throwable);
+    }
+  }
+
+  @Override
+  public boolean isInfoEnabled(Marker marker) {
+    return logger.isInfoEnabled(marker);
+  }
+
+  @Override
+  public void info(Marker marker, String s) {
+    logger.info(marker, s);
+  }
+
+  @Override
+  public void info(Marker marker, String s, Object o) {
+    logger.info(marker, s, o);
+  }
+
+  @Override
+  public void info(Marker marker, String s, Object o, Object o1) {
+    logger.info(marker, s, o, o1);
+  }
+
+  @Override
+  public void info(Marker marker, String s, Object... objects) {
+    logger.info(marker, s, objects);
+  }
+
+  @Override
+  public void info(Marker marker, String s, Throwable throwable) {
+    logger.info(marker, s, throwable);
+  }
+
+  @Override
+  public boolean isWarnEnabled() {
+    return logger.isWarnEnabled();
+  }
+
+  @Override
+  public void warn(String s) {
+    if (isEnabled()) {
+      DeferredLogCache.warn(logger, s);
+    } else {
+      logger.warn(s);
+    }
+  }
+
+  @Override
+  public void warn(String s, Object o) {
+    if (isEnabled()) {
+      DeferredLogCache.warn(logger, s, o);
+    } else {
+      logger.warn(s, o);
+    }
+  }
+
+  @Override
+  public void warn(String s, Object... objects) {
+    if (isEnabled()) {
+      DeferredLogCache.warn(logger, s, objects);
+    } else {
+      logger.warn(s, objects);
+    }
+  }
+
+  @Override
+  public void warn(String s, Object o, Object o1) {
+    if (isEnabled()) {
+      DeferredLogCache.warn(logger, s, o, o1);
+    } else {
+      logger.warn(s, o, o1);
+    }
+  }
+
+  @Override
+  public void warn(String s, Throwable throwable) {
+    if (isEnabled()) {
+      DeferredLogCache.warn(logger, s, throwable);
+    } else {
+      logger.warn(s, throwable);
+    }
+  }
+
+  @Override
+  public boolean isWarnEnabled(Marker marker) {
+    return logger.isWarnEnabled(marker);
+  }
+
+  @Override
+  public void warn(Marker marker, String s) {
+    logger.warn(marker, s);
+  }
+
+  @Override
+  public void warn(Marker marker, String s, Object o) {
+    logger.warn(marker, s, o);
+  }
+
+  @Override
+  public void warn(Marker marker, String s, Object o, Object o1) {
+    logger.warn(marker, s, o, o1);
+  }
+
+  @Override
+  public void warn(Marker marker, String s, Object... objects) {
+    logger.warn(marker, s, objects);
+  }
+
+  @Override
+  public void warn(Marker marker, String s, Throwable throwable) {
+    logger.warn(marker, s, throwable);
+  }
+
+  @Override
+  public boolean isErrorEnabled() {
+    return logger.isErrorEnabled();
+  }
+
+  @Override
+  public void error(String s) {
+    if (isEnabled()) {
+      DeferredLogCache.error(logger, s);
+    } else {
+      logger.error(s);
+    }
+  }
+
+  @Override
+  public void error(String s, Object o) {
+    if (isEnabled()) {
+      DeferredLogCache.error(logger, s, o);
+    } else {
+      logger.error(s, o);
+    }
+  }
+
+  @Override
+  public void error(String s, Object o, Object o1) {
+    if (isEnabled()) {
+      DeferredLogCache.error(logger, s, o, o1);
+    } else {
+      logger.error(s, o, o1);
+    }
+  }
+
+  @Override
+  public void error(String s, Object... objects) {
+    if (isEnabled()) {
+      DeferredLogCache.error(logger, s, objects);
+    } else {
+      logger.error(s, objects);
+    }
+  }
+
+  @Override
+  public void error(String s, Throwable throwable) {
+    if (isEnabled()) {
+      DeferredLogCache.error(logger, s, throwable);
+    } else {
+      logger.error(s, throwable);
+    }
+  }
+
+  @Override
+  public boolean isErrorEnabled(Marker marker) {
+    return logger.isErrorEnabled(marker);
+  }
+
+  @Override
+  public void error(Marker marker, String s) {
+    logger.error(marker, s);
+  }
+
+  @Override
+  public void error(Marker marker, String s, Object o) {
+    logger.error(marker, s, o);
+  }
+
+  @Override
+  public void error(Marker marker, String s, Object o, Object o1) {
+    logger.error(marker, s, o, o1);
+  }
+
+  @Override
+  public void error(Marker marker, String s, Object... objects) {
+    logger.error(marker, s, objects);
+  }
+
+  @Override
+  public void error(Marker marker, String s, Throwable throwable) {
+    logger.error(marker, s, throwable);
+  }
+}

+ 40 - 0
apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/DeferredLoggerFactory.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.apollo.core.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/19
+ */
+public class DeferredLoggerFactory {
+
+  private DeferredLoggerFactory() {
+  }
+
+  public static Logger getLogger(Class<?> clazz) {
+    Logger logger = LoggerFactory.getLogger(clazz);
+    return new DeferredLogger(logger);
+  }
+
+  public static Logger getLogger(String name) {
+    Logger logger = LoggerFactory.getLogger(name);
+    return new DeferredLogger(logger);
+  }
+}

+ 3 - 2
apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java

@@ -21,8 +21,8 @@ import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import java.util.Properties;
 
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.ctrip.framework.foundation.internals.Utils;
 import com.ctrip.framework.foundation.internals.io.BOMInputStream;
@@ -31,7 +31,8 @@ import com.ctrip.framework.foundation.spi.provider.Provider;
 
 public class DefaultApplicationProvider implements ApplicationProvider {
 
-  private static final Logger logger = LoggerFactory.getLogger(DefaultApplicationProvider.class);
+  private static final Logger logger = DeferredLoggerFactory
+      .getLogger(DefaultApplicationProvider.class);
   public static final String APP_PROPERTIES_CLASSPATH = "/META-INF/app.properties";
   private Properties m_appProperties = new Properties();
 

+ 13 - 7
apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultServerProvider.java

@@ -34,6 +34,7 @@
 
 package com.ctrip.framework.foundation.internals.provider;
 
+import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory;
 import com.google.common.base.Strings;
 import java.io.File;
 import java.io.FileInputStream;
@@ -47,10 +48,10 @@ import com.ctrip.framework.foundation.internals.io.BOMInputStream;
 import com.ctrip.framework.foundation.spi.provider.Provider;
 import com.ctrip.framework.foundation.spi.provider.ServerProvider;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class DefaultServerProvider implements ServerProvider {
-  private static final Logger logger = LoggerFactory.getLogger(DefaultServerProvider.class);
+
+  private static final Logger logger = DeferredLoggerFactory.getLogger(DefaultServerProvider.class);
 
   static final String DEFAULT_SERVER_PROPERTIES_PATH_ON_LINUX = "/opt/settings/server.properties";
   static final String DEFAULT_SERVER_PROPERTIES_PATH_ON_WINDOWS = "C:/opt/settings/server.properties";
@@ -66,7 +67,8 @@ public class DefaultServerProvider implements ServerProvider {
       return serverPropertiesPath;
     }
 
-    return Utils.isOSWindows() ? DEFAULT_SERVER_PROPERTIES_PATH_ON_WINDOWS : DEFAULT_SERVER_PROPERTIES_PATH_ON_LINUX;
+    return Utils.isOSWindows() ? DEFAULT_SERVER_PROPERTIES_PATH_ON_WINDOWS
+        : DEFAULT_SERVER_PROPERTIES_PATH_ON_LINUX;
   }
 
   private String getCustomizedServerPropertiesPath() {
@@ -110,7 +112,8 @@ public class DefaultServerProvider implements ServerProvider {
     try {
       if (in != null) {
         try {
-          m_serverProperties.load(new InputStreamReader(new BOMInputStream(in), StandardCharsets.UTF_8));
+          m_serverProperties
+              .load(new InputStreamReader(new BOMInputStream(in), StandardCharsets.UTF_8));
         } finally {
           in.close();
         }
@@ -189,7 +192,8 @@ public class DefaultServerProvider implements ServerProvider {
 
     // 4. Set environment to null.
     m_env = null;
-    logger.info("Environment is set to null. Because it is not available in either (1) JVM system property 'env', (2) OS env variable 'ENV' nor (3) property 'env' from the properties InputStream.");
+    logger.info(
+        "Environment is set to null. Because it is not available in either (1) JVM system property 'env', (2) OS env variable 'ENV' nor (3) property 'env' from the properties InputStream.");
   }
 
   private void initDataCenter() {
@@ -219,12 +223,14 @@ public class DefaultServerProvider implements ServerProvider {
 
     // 4. Set Data Center to null.
     m_dc = null;
-    logger.debug("Data Center is set to null. Because it is not available in either (1) JVM system property 'idc', (2) OS env variable 'IDC' nor (3) property 'idc' from the properties InputStream.");
+    logger.debug(
+        "Data Center is set to null. Because it is not available in either (1) JVM system property 'idc', (2) OS env variable 'IDC' nor (3) property 'idc' from the properties InputStream.");
   }
 
   @Override
   public String toString() {
-    return "environment [" + getEnvType() + "] data center [" + getDataCenter() + "] properties: " + m_serverProperties
+    return "environment [" + getEnvType() + "] data center [" + getDataCenter() + "] properties: "
+        + m_serverProperties
         + " (DefaultServerProvider)";
   }
 }

+ 69 - 0
apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/DeferredLogCacheTest.java

@@ -0,0 +1,69 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.apollo.core.utils;
+
+import com.ctrip.framework.test.tools.AloneRunner;
+import com.ctrip.framework.test.tools.AloneWith;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/20
+ */
+@RunWith(AloneRunner.class)
+@AloneWith(JUnit4.class)
+public class DeferredLogCacheTest {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private static final String logMsg = "hello kl";
+
+    @Test
+    public void testDeferredLogCacheMaxLogSize() {
+        for (int i = 0; i < 20000; i++) {
+            DeferredLogCache.info(logger, "DeferredLogUtilTest");
+        }
+        Assert.assertEquals(DeferredLogCache.logSize(), DeferredLogCache.MAX_LOG_SIZE);
+    }
+
+    @Test
+    public void testDisableDeferred() {
+        DeferredLogCache.clear();
+        DeferredLogger.disable();
+        final Logger defaultLogger = DeferredLoggerFactory.getLogger(DeferredLoggerTest.class);
+        defaultLogger.info(logMsg);
+        defaultLogger.debug(logMsg);
+        defaultLogger.warn(logMsg);
+        Assert.assertEquals(0, DeferredLogCache.logSize());
+
+    }
+
+    @Test
+    public void testEnableDeferred() {
+        final Logger defaultLogger = DeferredLoggerFactory.getLogger(DeferredLoggerTest.class);
+        DeferredLogger.enable();
+
+        defaultLogger.info(logMsg);
+        defaultLogger.debug(logMsg);
+        defaultLogger.warn(logMsg);
+        Assert.assertEquals(3, DeferredLogCache.logSize());
+    }
+}

+ 48 - 0
apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/DeferredLoggerStateTest.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.apollo.core.utils;
+
+import com.ctrip.framework.test.tools.AloneRunner;
+import com.ctrip.framework.test.tools.AloneWith;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/21
+ */
+@RunWith(AloneRunner.class)
+@AloneWith(JUnit4.class)
+public class DeferredLoggerStateTest {
+
+  @Test
+  public void testDeferredState() {
+    Assert.assertFalse(DeferredLogger.isEnabled());
+
+    DeferredLogger.enable();
+    Assert.assertTrue(DeferredLogger.isEnabled());
+
+    DeferredLogger.replayTo();
+    Assert.assertFalse(DeferredLogger.isEnabled());
+
+    DeferredLogger.enable();
+    Assert.assertFalse(DeferredLogger.isEnabled());
+  }
+
+}

+ 97 - 0
apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/DeferredLoggerTest.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.apollo.core.utils;
+
+import com.ctrip.framework.test.tools.AloneRunner;
+import com.ctrip.framework.test.tools.AloneWith;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.slf4j.Logger;
+
+/**
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/11
+ */
+@RunWith(AloneRunner.class)
+@AloneWith(JUnit4.class)
+public class DeferredLoggerTest {
+
+  private static ByteArrayOutputStream outContent;
+  private static Logger logger = null;
+  private static PrintStream printStream;
+
+  @BeforeClass
+  public static void init() throws NoSuchFieldException, IllegalAccessException {
+    DeferredLoggerTest.outContent = new ByteArrayOutputStream();
+    DeferredLoggerTest.printStream = new PrintStream(DeferredLoggerTest.outContent);
+    System.setOut(DeferredLoggerTest.printStream);
+    DeferredLoggerTest.logger = DeferredLoggerFactory.getLogger("DeferredLoggerTest");
+  }
+
+  @Test
+  public void testErrorLog() {
+    DeferredLoggerTest.logger.error("errorLogger");
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("errorLogger"));
+  }
+
+  @Test
+  public void testInfoLog() {
+    DeferredLoggerTest.logger.info("inFoLogger");
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("inFoLogger"));
+  }
+
+  @Test
+  public void testWarnLog() {
+    DeferredLoggerTest.logger.warn("warnLogger");
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("warnLogger"));
+  }
+
+  @Test
+  public void testDebugLog() {
+    DeferredLoggerTest.logger.warn("debugLogger");
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("debugLogger"));
+  }
+
+  @Test
+  public void testDeferredLog() {
+    DeferredLogger.enable();
+
+    DeferredLoggerTest.logger.error("errorLogger_testDeferredLog");
+    DeferredLoggerTest.logger.info("inFoLogger_testDeferredLog");
+    DeferredLoggerTest.logger.warn("warnLogger_testDeferredLog");
+    DeferredLoggerTest.logger.debug("debugLogger_testDeferredLog");
+
+    Assert.assertFalse(DeferredLoggerTest.outContent.toString().contains("errorLogger_testDeferredLog"));
+    Assert.assertFalse(DeferredLoggerTest.outContent.toString().contains("inFoLogger_testDeferredLog"));
+    Assert.assertFalse(DeferredLoggerTest.outContent.toString().contains("warnLogger_testDeferredLog"));
+    Assert.assertFalse(DeferredLoggerTest.outContent.toString().contains("debugLogger_testDeferredLog"));
+
+    DeferredLogCache.replayTo();
+
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("errorLogger_testDeferredLog"));
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("inFoLogger_testDeferredLog"));
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("warnLogger_testDeferredLog"));
+    Assert.assertTrue(DeferredLoggerTest.outContent.toString().contains("debugLogger_testDeferredLog"));
+
+  }
+
+}

+ 43 - 0
apollo-core/src/test/java/com/ctrip/framework/test/tools/AloneClassLoader.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.test.tools;
+
+import java.net.URLClassLoader;
+
+/**
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/21
+ */
+public class AloneClassLoader extends URLClassLoader {
+
+  private final ClassLoader appClassLoader;
+
+  public AloneClassLoader() {
+    super(((URLClassLoader) getSystemClassLoader()).getURLs(),
+        Thread.currentThread().getContextClassLoader().getParent());
+    appClassLoader = Thread.currentThread().getContextClassLoader();
+  }
+
+  @Override
+  public Class<?> loadClass(String name) throws ClassNotFoundException {
+    if (name.startsWith("org.junit.") || name.startsWith("junit.")) {
+      return appClassLoader.loadClass(name);
+    }
+
+    return super.loadClass(name);
+  }
+}

+ 85 - 0
apollo-core/src/test/java/com/ctrip/framework/test/tools/AloneRunner.java

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.test.tools;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.InitializationError;
+
+/**
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/21
+ */
+public class AloneRunner extends Runner {
+
+  private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
+  private final Runner realRunner;
+
+  private final ClassLoader testCaseClassloader;
+
+  public AloneRunner(Class<?> clazz) throws InitializationError {
+
+    AloneWith annotation = clazz.getAnnotation(
+        AloneWith.class);
+
+    Class<? extends Runner> realClassRunnerClass =
+        annotation == null ? JUnit4.class : annotation.value();
+    if (AloneRunner.class.isAssignableFrom(realClassRunnerClass)) {
+      throw new InitializationError("Dead-loop code");
+    }
+
+    testCaseClassloader = new AloneClassLoader();
+    ClassLoader backupClassLoader = Thread.currentThread().getContextClassLoader();
+    Thread.currentThread().setContextClassLoader(testCaseClassloader);
+    try {
+      Class<?> newTestCaseClass = testCaseClassloader.loadClass(clazz.getName());
+      Class<? extends Runner> realRunnerClass = (Class<? extends Runner>) testCaseClassloader
+          .loadClass(realClassRunnerClass.getName());
+      realRunner = buildRunner(realRunnerClass, newTestCaseClass);
+    } catch (ReflectiveOperationException e) {
+      throw new InitializationError(e);
+    } finally {
+      Thread.currentThread().setContextClassLoader(backupClassLoader);
+    }
+  }
+
+  public Description getDescription() {
+    return realRunner.getDescription();
+  }
+
+  public void run(RunNotifier runNotifier) {
+    ClassLoader backupClassLoader = Thread.currentThread().getContextClassLoader();
+    Thread.currentThread().setContextClassLoader(testCaseClassloader);
+
+    realRunner.run(runNotifier);
+
+    Thread.currentThread().setContextClassLoader(backupClassLoader);
+  }
+
+  protected Runner buildRunner(Class<? extends Runner> runnerClass,
+      Class<?> testClass) throws ReflectiveOperationException, InitializationError {
+    try {
+      return runnerClass.getConstructor(Class.class).newInstance(testClass);
+    } catch (NoSuchMethodException e) {
+      String simpleName = runnerClass.getSimpleName();
+      throw new InitializationError(String.format(
+          CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
+    }
+  }
+}

+ 36 - 0
apollo-core/src/test/java/com/ctrip/framework/test/tools/AloneWith.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 Apollo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.ctrip.framework.test.tools;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.runner.Runner;
+
+/**
+ * @author kl (http://kailing.pub)
+ * @since 2021/5/21
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Inherited
+public @interface AloneWith {
+
+  Class<? extends Runner> value();
+}

+ 1 - 1
apollo-core/src/test/resources/log4j2.xml

@@ -28,7 +28,7 @@
         <logger name="com.ctrip.framework.apollo" additivity="false" level="trace">
             <AppenderRef ref="Async" level="WARN"/>
         </logger>
-        <root level="INFO">
+        <root level="debug">
             <AppenderRef ref="Async"/>
         </root>
     </loggers>

+ 2 - 0
apollo-demo/src/main/resources/application.yml

@@ -16,5 +16,7 @@
 apollo:
   bootstrap:
     enabled: true
+    eagerLoad:
+      enabled: true
     # will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase
     namespaces: application,TEST1.apollo,application.yaml

+ 1 - 1
docs/zh/usage/java-sdk-user-guide.md

@@ -567,7 +567,7 @@ Spring Boot除了支持上述两种集成方式以外,还支持通过applicati
 
 3. 将Apollo配置加载提到初始化日志系统之前(1.2.0+)
 
-从1.2.0版本开始,如果希望把日志相关的配置(如`logging.level.root=info`或`logback-spring.xml`中的参数)也放在Apollo管理,那么可以额外配置`apollo.bootstrap.eagerLoad.enabled=true`来使Apollo的加载顺序放到日志系统加载之前,不过这会导致Apollo的启动过程无法通过日志的方式输出(因为执行Apollo加载的时候,日志系统压根没有准备好呢!所以在Apollo代码中使用Slf4j的日志输出便没有任何内容),更多信息可以参考[PR 1614](https://github.com/ctripcorp/apollo/pull/1614)。参考配置示例如下:
+从1.2.0版本开始,如果希望把日志相关的配置(如`logging.level.root=info`或`logback-spring.xml`中的参数)也放在Apollo管理,那么可以额外配置`apollo.bootstrap.eagerLoad.enabled=true`来使Apollo的加载顺序放到日志系统加载之前,更多信息可以参考[PR 1614](https://github.com/ctripcorp/apollo/pull/1614)。参考配置示例如下:
 
 ```properties
      # will inject 'application' namespace in bootstrap phase