From 366a9b4a3ed845f5747ca9e5bd457e99ddf34253 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 18 Feb 2021 17:14:21 +1100 Subject: [PATCH] Display DevServices logs at build time --- .../logging/LoggingResourceProcessor.java | 30 ++- .../deployment/steps/MainClassBuildStep.java | 9 +- .../runtime/graal/LoggingSubstitutions.java | 4 +- .../runtime/logging/CleanupFilterConfig.java | 4 +- .../runtime/logging/LoggingSetupRecorder.java | 64 ++++- .../CategoryConfiguredHandlerTest.java | 4 +- .../quarkus/logging/LoggingTestsHelper.java | 4 +- .../json/JsonFormatterDefaultConfigTest.java | 4 +- .../logging/sentry/SentryLoggerTest.java | 4 +- .../logging/InitialConfigurator.java | 23 +- .../logging/QuarkusDelayedHandler.java | 219 ++++++++++++++++++ 11 files changed, 327 insertions(+), 42 deletions(-) create mode 100644 independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/logging/QuarkusDelayedHandler.java 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 handlerBuildItems, List namedHandlerBuildItems, List consoleFormatItems, - Optional possibleBannerBuildItem) { + Optional possibleBannerBuildItem, + LaunchModeBuildItem launchModeBuildItem, + List logCleanupFilters) { final List>> 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 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 ifStartsWith; + public List 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 categories, + public static void initializeBuildTimeLogging(LogConfig config, LogBuildTimeConfig buildConfig) { + + final Map categories = config.categories; + final LogContext logContext = LogContext.getLogContext(); + final Logger rootLogger = logContext.getLogger(""); + + rootLogger.setLevel(config.level); + + ErrorManager errorManager = new OnlyOnceErrorManager(); + final Map filters = config.filters; + List filterElements = new ArrayList<>(filters.size()); + for (Entry entry : filters.entrySet()) { + filterElements.add( + new LogCleanupFilterElement(entry.getKey(), entry.getValue().targetLevel, entry.getValue().ifStartsWith)); + } + + final ArrayList 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 namedHandlers = createNamedHandlers(config, Collections.emptyList(), errorManager, filterElements); + + for (Map.Entry 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 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 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 categories) { + private static CategoryBuildTimeConfig isSubsetOf(String categoryName, Map categories) { return categories.entrySet().stream() .filter(buildCategoryEntry -> categoryName.startsWith(buildCategoryEntry.getKey())) .map(Entry::getValue) @@ -233,7 +291,7 @@ private static void addToNamedHandlers(Map namedHandlers, Handl namedHandlers.put(handlerName, handler); } - private void addNamedHandlersToCategory(CategoryConfig categoryConfig, Map namedHandlers, + private static void addNamedHandlersToCategory(CategoryConfig categoryConfig, Map 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 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} + *

+ * 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. + *

+ * + * @see #setHandlers(Handler[]) + */ + @Override + public void addHandler(final Handler handler) throws SecurityException { + super.addHandler(handler); + activate(); + } + + /** + * {@inheritDoc} + *

+ * Note that once this is invoked the handler will be activated and the messages will no longer be queued. + *

+ */ + @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} + *

+ * Note that if the last child handler is removed the handler will no longer be activated and the messages will + * again be queued. + *

+ * + * @see #clearHandlers() + */ + @Override + public void removeHandler(final Handler handler) throws SecurityException { + super.removeHandler(handler); + activated = handlers.length != 0; + } + + /** + * {@inheritDoc} + *

+ * Note that once this is invoked the handler will no longer be activated and messages will again be queued. + *

+ * + * @see #removeHandler(Handler) + */ + @Override + public Handler[] clearHandlers() throws SecurityException { + activated = false; + return super.clearHandlers(); + } + + /** + * {@inheritDoc} + *

+ * This can be overridden to always require the caller calculation by setting the + * {@link #setCallerCalculationRequired(boolean)} value to {@code true}. + *

+ * + * @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. + *

+ * 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. + *

+ *

+ * 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. + *

+ * + * @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; + } +}