diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java
index 19b8841bcb9032..ab831ddb50d02c 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java
@@ -14,6 +14,7 @@
 import org.jboss.logmanager.EmbeddedConfigurator;
 import org.objectweb.asm.Opcodes;
 
+import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
 import io.quarkus.bootstrap.logging.InitialConfigurator;
 import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
 import io.quarkus.deployment.annotations.BuildProducer;
@@ -22,6 +23,7 @@
 import io.quarkus.deployment.annotations.Record;
 import io.quarkus.deployment.builditem.ConsoleFormatterBannerBuildItem;
 import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
+import io.quarkus.deployment.builditem.LaunchModeBuildItem;
 import io.quarkus.deployment.builditem.LogCategoryBuildItem;
 import io.quarkus.deployment.builditem.LogConsoleFormatBuildItem;
 import io.quarkus.deployment.builditem.LogHandlerBuildItem;
@@ -44,10 +46,14 @@
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.MethodDescriptor;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.runtime.LaunchMode;
 import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.configuration.ConfigInstantiator;
 import io.quarkus.runtime.logging.CategoryBuildTimeConfig;
+import io.quarkus.runtime.logging.CleanupFilterConfig;
 import io.quarkus.runtime.logging.InheritableLevel;
 import io.quarkus.runtime.logging.LogBuildTimeConfig;
+import io.quarkus.runtime.logging.LogCleanupFilterElement;
 import io.quarkus.runtime.logging.LogConfig;
 import io.quarkus.runtime.logging.LogMetricsHandlerRecorder;
 import io.quarkus.runtime.logging.LoggingSetupRecorder;
@@ -124,7 +130,9 @@ void miscSetup(
     LoggingSetupBuildItem setupLoggingRuntimeInit(LoggingSetupRecorder recorder, LogConfig log, LogBuildTimeConfig buildLog,
             List<LogHandlerBuildItem> handlerBuildItems,
             List<NamedLogHandlersBuildItem> namedHandlerBuildItems, List<LogConsoleFormatBuildItem> consoleFormatItems,
-            Optional<ConsoleFormatterBannerBuildItem> possibleBannerBuildItem) {
+            Optional<ConsoleFormatterBannerBuildItem> possibleBannerBuildItem,
+            LaunchModeBuildItem launchModeBuildItem,
+            List<LogCleanupFilterBuildItem> logCleanupFilters) {
         final List<RuntimeValue<Optional<Handler>>> handlers = handlerBuildItems.stream()
                 .map(LogHandlerBuildItem::getHandlerValue)
                 .collect(Collectors.toList());
@@ -142,6 +150,26 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(LoggingSetupRecorder recorder, Log
         recorder.initializeLogging(log, buildLog, handlers, namedHandlers,
                 consoleFormatItems.stream().map(LogConsoleFormatBuildItem::getFormatterValue).collect(Collectors.toList()),
                 possibleSupplier);
+        if (launchModeBuildItem.getLaunchMode() != LaunchMode.NORMAL) {
+            LogConfig logConfig = new LogConfig();
+            ConfigInstantiator.handleObject(logConfig);
+            for (LogCleanupFilterBuildItem i : logCleanupFilters) {
+                CleanupFilterConfig value = new CleanupFilterConfig();
+                LogCleanupFilterElement filterElement = i.getFilterElement();
+                value.ifStartsWith = filterElement.getMessageStarts();
+                value.targetLevel = filterElement.getTargetLevel() == null ? org.jboss.logmanager.Level.DEBUG
+                        : filterElement.getTargetLevel();
+                logConfig.filters.put(filterElement.getLoggerName(), value);
+            }
+            LoggingSetupRecorder.initializeBuildTimeLogging(logConfig, buildLog);
+            ((QuarkusClassLoader) Thread.currentThread().getContextClassLoader()).addCloseTask(new Runnable() {
+                @Override
+                public void run() {
+                    InitialConfigurator.DELAYED_HANDLER.buildTimeComplete();
+                }
+            });
+        }
+
         return new LoggingSetupBuildItem();
     }
 
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
index a17e1e5e4a8e99..433b625a7071ee 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
@@ -25,9 +25,9 @@
 import org.jboss.jandex.MethodInfo;
 import org.jboss.jandex.Type;
 import org.jboss.logging.Logger;
-import org.jboss.logmanager.handlers.DelayedHandler;
 
 import io.quarkus.bootstrap.logging.InitialConfigurator;
+import io.quarkus.bootstrap.logging.QuarkusDelayedHandler;
 import io.quarkus.bootstrap.runner.Timing;
 import io.quarkus.builder.Version;
 import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
@@ -288,13 +288,14 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
 
         // an exception was thrown before logging was actually setup, we simply dump everything to the console
         ResultHandle delayedHandler = cb
-                .readStaticField(FieldDescriptor.of(InitialConfigurator.class, "DELAYED_HANDLER", DelayedHandler.class));
-        ResultHandle isActivated = cb.invokeVirtualMethod(ofMethod(DelayedHandler.class, "isActivated", boolean.class),
+                .readStaticField(FieldDescriptor.of(InitialConfigurator.class, "DELAYED_HANDLER", QuarkusDelayedHandler.class));
+        ResultHandle isActivated = cb.invokeVirtualMethod(ofMethod(QuarkusDelayedHandler.class, "isActivated", boolean.class),
                 delayedHandler);
         BytecodeCreator isActivatedFalse = cb.ifNonZero(isActivated).falseBranch();
         ResultHandle handlersArray = isActivatedFalse.newArray(Handler.class, 1);
         isActivatedFalse.writeArrayValue(handlersArray, 0, isActivatedFalse.newInstance(ofConstructor(ConsoleHandler.class)));
-        isActivatedFalse.invokeVirtualMethod(ofMethod(DelayedHandler.class, "setHandlers", Handler[].class, Handler[].class),
+        isActivatedFalse.invokeVirtualMethod(
+                ofMethod(QuarkusDelayedHandler.class, "setHandlers", Handler[].class, Handler[].class),
                 delayedHandler, handlersArray);
         isActivatedFalse.breakScope();
 
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/LoggingSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/LoggingSubstitutions.java
index e97cbce756a57c..2b1967c2b7a690 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/graal/LoggingSubstitutions.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/LoggingSubstitutions.java
@@ -3,7 +3,6 @@
 import java.util.logging.Handler;
 
 import org.jboss.logmanager.LogContext;
-import org.jboss.logmanager.handlers.DelayedHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -13,6 +12,7 @@
 import com.oracle.svm.core.annotate.TargetClass;
 
 import io.quarkus.bootstrap.logging.InitialConfigurator;
+import io.quarkus.bootstrap.logging.QuarkusDelayedHandler;
 
 /**
  */
@@ -38,7 +38,7 @@ public static Logger getLogger(Class<?> clazz) {
 final class Target_io_quarkus_runtime_logging_InitialConfigurator {
     @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)
     @Alias
-    public static DelayedHandler DELAYED_HANDLER = new DelayedHandler();
+    public static QuarkusDelayedHandler DELAYED_HANDLER = new QuarkusDelayedHandler();
 }
 
 @TargetClass(java.util.logging.Logger.class)
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java
index afb95ee8a40185..ad56bdf5320a6c 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java
@@ -12,11 +12,11 @@ public class CleanupFilterConfig {
      * The message starts to match
      */
     @ConfigItem(defaultValue = "inherit")
-    List<String> ifStartsWith;
+    public List<String> ifStartsWith;
 
     /**
      * The new log level for the filtered message, defaults to DEBUG
      */
     @ConfigItem(defaultValue = "DEBUG")
-    Level targetLevel;
+    public Level targetLevel;
 }
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java
index 45b795f244c235..3bd50a147ad0ce 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java
@@ -178,7 +178,65 @@ public void initializeLogging(LogConfig config, LogBuildTimeConfig buildConfig,
         InitialConfigurator.DELAYED_HANDLER.setHandlers(handlers.toArray(EmbeddedConfigurator.NO_HANDLERS));
     }
 
-    private Level getLogLevel(String categoryName, CategoryConfig categoryConfig, Map<String, CategoryConfig> categories,
+    public static void initializeBuildTimeLogging(LogConfig config, LogBuildTimeConfig buildConfig) {
+
+        final Map<String, CategoryConfig> categories = config.categories;
+        final LogContext logContext = LogContext.getLogContext();
+        final Logger rootLogger = logContext.getLogger("");
+
+        rootLogger.setLevel(config.level);
+
+        ErrorManager errorManager = new OnlyOnceErrorManager();
+        final Map<String, CleanupFilterConfig> filters = config.filters;
+        List<LogCleanupFilterElement> filterElements = new ArrayList<>(filters.size());
+        for (Entry<String, CleanupFilterConfig> entry : filters.entrySet()) {
+            filterElements.add(
+                    new LogCleanupFilterElement(entry.getKey(), entry.getValue().targetLevel, entry.getValue().ifStartsWith));
+        }
+
+        final ArrayList<Handler> handlers = new ArrayList<>(3);
+
+        if (config.console.enable) {
+            final Handler consoleHandler = configureConsoleHandler(config.console, errorManager, filterElements,
+                    Collections.emptyList(), new RuntimeValue<>(Optional.empty()));
+            errorManager = consoleHandler.getErrorManager();
+            handlers.add(consoleHandler);
+        }
+
+        Map<String, Handler> namedHandlers = createNamedHandlers(config, Collections.emptyList(), errorManager, filterElements);
+
+        for (Map.Entry<String, CategoryConfig> entry : categories.entrySet()) {
+            final CategoryBuildTimeConfig buildCategory = isSubsetOf(entry.getKey(), buildConfig.categories);
+            final Level logLevel = getLogLevel(entry.getKey(), entry.getValue(), categories, buildConfig.minLevel);
+            final Level minLogLevel = buildCategory == null
+                    ? buildConfig.minLevel
+                    : buildCategory.minLevel.getLevel();
+
+            if (logLevel.intValue() < minLogLevel.intValue()) {
+                log.warnf("Log level %s for category '%s' set below minimum logging level %s, promoting it to %s", logLevel,
+                        entry.getKey(), minLogLevel, minLogLevel);
+
+                entry.getValue().level = InheritableLevel.of(minLogLevel.toString());
+            }
+        }
+
+        for (Map.Entry<String, CategoryConfig> entry : categories.entrySet()) {
+            final String name = entry.getKey();
+            final Logger categoryLogger = logContext.getLogger(name);
+            final CategoryConfig categoryConfig = entry.getValue();
+            if (!categoryConfig.level.isInherited()) {
+                categoryLogger.setLevel(categoryConfig.level.getLevel());
+            }
+            categoryLogger.setUseParentHandlers(categoryConfig.useParentHandlers);
+            if (categoryConfig.handlers.isPresent()) {
+                addNamedHandlersToCategory(categoryConfig, namedHandlers, categoryLogger, errorManager);
+            }
+        }
+        InitialConfigurator.DELAYED_HANDLER.setAutoFlush(false);
+        InitialConfigurator.DELAYED_HANDLER.setBuildTimeHandlers(handlers.toArray(EmbeddedConfigurator.NO_HANDLERS));
+    }
+
+    private static Level getLogLevel(String categoryName, CategoryConfig categoryConfig, Map<String, CategoryConfig> categories,
             Level rootMinLevel) {
         if (Objects.isNull(categoryConfig))
             return rootMinLevel;
@@ -195,7 +253,7 @@ private Level getLogLevel(String categoryName, CategoryConfig categoryConfig, Ma
         return getLogLevel(parent, categories.get(parent), categories, rootMinLevel);
     }
 
-    private CategoryBuildTimeConfig isSubsetOf(String categoryName, Map<String, CategoryBuildTimeConfig> categories) {
+    private static CategoryBuildTimeConfig isSubsetOf(String categoryName, Map<String, CategoryBuildTimeConfig> categories) {
         return categories.entrySet().stream()
                 .filter(buildCategoryEntry -> categoryName.startsWith(buildCategoryEntry.getKey()))
                 .map(Entry::getValue)
@@ -233,7 +291,7 @@ private static void addToNamedHandlers(Map<String, Handler> namedHandlers, Handl
         namedHandlers.put(handlerName, handler);
     }
 
-    private void addNamedHandlersToCategory(CategoryConfig categoryConfig, Map<String, Handler> namedHandlers,
+    private static void addNamedHandlersToCategory(CategoryConfig categoryConfig, Map<String, Handler> namedHandlers,
             Logger categoryLogger,
             ErrorManager errorManager) {
         for (String categoryNamedHandler : categoryConfig.handlers.get()) {
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java
index b5937e805cb107..5ccd126e0e4351 100644
--- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java
@@ -7,7 +7,6 @@
 
 import org.jboss.logmanager.formatters.PatternFormatter;
 import org.jboss.logmanager.handlers.ConsoleHandler;
-import org.jboss.logmanager.handlers.DelayedHandler;
 import org.jboss.logmanager.handlers.FileHandler;
 import org.jboss.shrinkwrap.api.ShrinkWrap;
 import org.jboss.shrinkwrap.api.spec.JavaArchive;
@@ -15,6 +14,7 @@
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 import io.quarkus.bootstrap.logging.InitialConfigurator;
+import io.quarkus.bootstrap.logging.QuarkusDelayedHandler;
 import io.quarkus.test.QuarkusUnitTest;
 
 public class CategoryConfiguredHandlerTest {
@@ -30,7 +30,7 @@ public void consoleOutputTest() {
         LogManager logManager = LogManager.getLogManager();
         assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class);
 
-        DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
+        QuarkusDelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
         assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler);
 
         Handler handler = Arrays.stream(delayedHandler.getHandlers()).filter(h -> (h instanceof ConsoleHandler))
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java
index b103f2dd1652db..d684669240cea7 100644
--- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java
@@ -9,10 +9,10 @@
 import java.util.logging.LogManager;
 import java.util.logging.Logger;
 
-import org.jboss.logmanager.handlers.DelayedHandler;
 import org.junit.jupiter.api.Assertions;
 
 import io.quarkus.bootstrap.logging.InitialConfigurator;
+import io.quarkus.bootstrap.logging.QuarkusDelayedHandler;
 
 public class LoggingTestsHelper {
 
@@ -20,7 +20,7 @@ public static Handler getHandler(Class clazz) {
         LogManager logManager = LogManager.getLogManager();
         assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class);
 
-        DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
+        QuarkusDelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
         assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler);
         assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL);
 
diff --git a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java
index a13945bd909cd3..7df4ae1110bcf9 100644
--- a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java
+++ b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java
@@ -14,11 +14,11 @@
 import org.jboss.logmanager.formatters.JsonFormatter;
 import org.jboss.logmanager.formatters.StructuredFormatter;
 import org.jboss.logmanager.handlers.ConsoleHandler;
-import org.jboss.logmanager.handlers.DelayedHandler;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 import io.quarkus.bootstrap.logging.InitialConfigurator;
+import io.quarkus.bootstrap.logging.QuarkusDelayedHandler;
 import io.quarkus.test.QuarkusUnitTest;
 
 public class JsonFormatterDefaultConfigTest {
@@ -43,7 +43,7 @@ public static JsonFormatter getJsonFormatter() {
         LogManager logManager = LogManager.getLogManager();
         assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class);
 
-        DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
+        QuarkusDelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
         assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler);
         assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL);
 
diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java
index a41b13317eb77c..05295b617bb296 100644
--- a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java
+++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java
@@ -8,11 +8,11 @@
 import java.util.logging.LogManager;
 import java.util.logging.Logger;
 
-import org.jboss.logmanager.handlers.DelayedHandler;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 import io.quarkus.bootstrap.logging.InitialConfigurator;
+import io.quarkus.bootstrap.logging.QuarkusDelayedHandler;
 import io.quarkus.test.QuarkusUnitTest;
 import io.sentry.HubAdapter;
 import io.sentry.Sentry;
@@ -41,7 +41,7 @@ public static Handler getSentryHandler() {
         LogManager logManager = LogManager.getLogManager();
         assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class);
 
-        DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
+        QuarkusDelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER;
         assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler);
         assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL);
 
diff --git a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/InitialConfigurator.java b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/InitialConfigurator.java
index fa062ae8f04bff..f15c3d93c2c4e9 100644
--- a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/InitialConfigurator.java
+++ b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/InitialConfigurator.java
@@ -6,34 +6,13 @@
 import org.jboss.logmanager.EmbeddedConfigurator;
 import org.jboss.logmanager.formatters.PatternFormatter;
 import org.jboss.logmanager.handlers.ConsoleHandler;
-import org.jboss.logmanager.handlers.DelayedHandler;
 
 /**
  *
  */
 public final class InitialConfigurator implements EmbeddedConfigurator {
 
-    public static final DelayedHandler DELAYED_HANDLER;
-
-    static {
-        //a hack around class loading
-        //this is always loaded in the root class loader with jboss-logmanager,
-        //however it may also be loaded in an isolated CL when running in dev
-        //or test mode. If it is in an isolated CL we load the handler from
-        //the class on the system class loader so they are equal
-        //TODO: should this class go in its own module and be excluded from isolated class loading?
-        DelayedHandler handler = new DelayedHandler();
-        ClassLoader cl = InitialConfigurator.class.getClassLoader();
-        try {
-            Class<?> root = Class.forName(InitialConfigurator.class.getName(), false, ClassLoader.getSystemClassLoader());
-            if (root.getClassLoader() != cl) {
-                handler = (DelayedHandler) root.getDeclaredField("DELAYED_HANDLER").get(null);
-            }
-        } catch (Exception e) {
-            //ignore, happens on native image build
-        }
-        DELAYED_HANDLER = handler;
-    }
+    public static final QuarkusDelayedHandler DELAYED_HANDLER = new QuarkusDelayedHandler();
 
     @Override
     public Level getMinimumLevelOf(final String loggerName) {
diff --git a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/QuarkusDelayedHandler.java b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/QuarkusDelayedHandler.java
new file mode 100644
index 00000000000000..6fe4b7ed49268d
--- /dev/null
+++ b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/QuarkusDelayedHandler.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * 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 io.quarkus.bootstrap.logging;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Logger;
+import org.jboss.logmanager.ExtHandler;
+import org.jboss.logmanager.ExtLogRecord;
+import org.jboss.logmanager.StandardOutputStreams;
+import org.jboss.logmanager.formatters.PatternFormatter;
+
+/**
+ * A handler that queues messages until it's at least one child handler is {@linkplain #addHandler(Handler) added} or
+ * {@linkplain #setHandlers(Handler[]) set}. If the children handlers are {@linkplain #clearHandlers() cleared} then
+ * the handler is no longer considered activated and messages will once again be queued.
+ */
+@SuppressWarnings({ "unused", "WeakerAccess" })
+public class QuarkusDelayedHandler extends ExtHandler {
+
+    private final Deque<ExtLogRecord> logRecords = new ArrayDeque<>();
+
+    private volatile boolean buildTimeLoggingActivated = false;
+    private volatile boolean activated = false;
+    private volatile boolean callerCalculationRequired = false;
+
+    @Override
+    protected void doPublish(final ExtLogRecord record) {
+        // If activated just delegate
+        if (activated) {
+            publishToNestedHandlers(record);
+            super.doPublish(record);
+        } else {
+            synchronized (this) {
+                // Check one more time to see if we've been activated before queuing the messages
+                if (activated || buildTimeLoggingActivated) {
+                    publishToNestedHandlers(record);
+                    super.doPublish(record);
+                } else {
+                    // Determine if we need to calculate the caller information before we queue the record
+                    if (isCallerCalculationRequired()) {
+                        // prepare record to move to another thread
+                        record.copyAll();
+                    } else {
+                        // Disable the caller calculation since it's been determined we won't be using it
+                        record.disableCallerCalculation();
+                        // Copy the MDC over
+                        record.copyMdc();
+                    }
+                    logRecords.addLast(record);
+                }
+            }
+        }
+    }
+
+    @Override
+    public final void close() throws SecurityException {
+        synchronized (this) {
+            if (!logRecords.isEmpty()) {
+                Formatter formatter = getFormatter();
+                if (formatter == null) {
+                    formatter = new PatternFormatter("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n");
+                }
+                StandardOutputStreams.printError("The DelayedHandler was closed before any children handlers were " +
+                        "configured. Messages will be written to stderr.");
+                // Always attempt to drain the queue
+                ExtLogRecord record;
+                while ((record = logRecords.pollFirst()) != null) {
+                    StandardOutputStreams.printError(formatter.format(record));
+                }
+            }
+        }
+        activated = false;
+        super.close();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Note that once this is invoked the handler will be activated and the messages will no longer be queued. If more
+     * than one child handler is required the {@link #setHandlers(Handler[])} should be used.
+     * </p>
+     *
+     * @see #setHandlers(Handler[])
+     */
+    @Override
+    public void addHandler(final Handler handler) throws SecurityException {
+        super.addHandler(handler);
+        activate();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Note that once this is invoked the handler will be activated and the messages will no longer be queued.
+     * </p>
+     */
+    @Override
+    public Handler[] setHandlers(final Handler[] newHandlers) throws SecurityException {
+        final Handler[] result = super.setHandlers(newHandlers);
+        activate();
+        return result;
+    }
+
+    public synchronized Handler[] setBuildTimeHandlers(final Handler[] newHandlers) throws SecurityException {
+        final Handler[] result = super.setHandlers(newHandlers);
+        buildTimeLoggingActivated = true;
+        ExtLogRecord record;
+        while ((record = logRecords.pollFirst()) != null) {
+            if (isEnabled() && isLoggable(record) && Logger.getLogger(record.getLoggerName()).isLoggable(record.getLevel())) {
+                publishToNestedHandlers(record);
+            }
+        }
+        return result;
+    }
+
+    public synchronized void buildTimeComplete() {
+        buildTimeLoggingActivated = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Note that if the last child handler is removed the handler will no longer be activated and the messages will
+     * again be queued.
+     * </p>
+     *
+     * @see #clearHandlers()
+     */
+    @Override
+    public void removeHandler(final Handler handler) throws SecurityException {
+        super.removeHandler(handler);
+        activated = handlers.length != 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Note that once this is invoked the handler will no longer be activated and messages will again be queued.
+     * </p>
+     *
+     * @see #removeHandler(Handler)
+     */
+    @Override
+    public Handler[] clearHandlers() throws SecurityException {
+        activated = false;
+        return super.clearHandlers();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This can be overridden to always require the caller calculation by setting the
+     * {@link #setCallerCalculationRequired(boolean)} value to {@code true}.
+     * </p>
+     *
+     * @see #setCallerCalculationRequired(boolean)
+     */
+    @Override
+    public boolean isCallerCalculationRequired() {
+        return callerCalculationRequired || super.isCallerCalculationRequired();
+    }
+
+    /**
+     * Sets whether or not {@linkplain ExtLogRecord#copyAll() caller information} will be required when formatting
+     * records.
+     * <p>
+     * If set to {@code true} the {@linkplain ExtLogRecord#copyAll() caller information} will be calculated for each
+     * record that is placed in the queue. A value of {@code false} means the
+     * {@link super#isCallerCalculationRequired()} will be used.
+     * </p>
+     * <p>
+     * Note that the caller information is only attempted to be calculated when the handler has not been activated. Once
+     * activated it's up to the {@linkplain #getHandlers() children handlers} to determine how the record is processed.
+     * </p>
+     *
+     * @param callerCalculationRequired {@code true} if the {@linkplain ExtLogRecord#copyAll() caller information}
+     *        should always be calculated before the record is being placed in the queue
+     */
+    public void setCallerCalculationRequired(final boolean callerCalculationRequired) {
+        this.callerCalculationRequired = callerCalculationRequired;
+    }
+
+    /**
+     * Indicates whether or not this handler has been activated.
+     *
+     * @return {@code true} if the handler has been activated, otherwise {@code false}
+     */
+    public final boolean isActivated() {
+        return activated;
+    }
+
+    private synchronized void activate() {
+        // Always attempt to drain the queue
+        ExtLogRecord record;
+        while ((record = logRecords.pollFirst()) != null) {
+            if (isEnabled() && isLoggable(record) && Logger.getLogger(record.getLoggerName()).isLoggable(record.getLevel())) {
+                publishToNestedHandlers(record);
+            }
+        }
+        activated = true;
+    }
+}