From 7580a2615ea2eebd4eb63e0f5d1432c5c6ad7842 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 3 Sep 2021 16:39:44 +1000 Subject: [PATCH] Make Dev Services logging nicer This changes the mass of Dev Services logging to a single status line per starting container. This allows the user to see that progress is happening, without flooding the console. If the operation fails all captured output is dumped. Fixes #16203 --- .../deployment/console/AeshConsole.java | 28 +++-- .../deployment/console/ConsoleHelper.java | 26 +---- .../console/ConsoleInstalledBuildItem.java | 13 +++ .../deployment/console/ConsoleProcessor.java | 8 +- .../console/StartupLogCompressor.java | 79 +++++++++++++ .../dev/testing/JunitTestRunner.java | 2 +- .../quarkus/dev/console/QuarkusConsole.java | 23 ++++ .../DevServicesApicurioRegistryProcessor.java | 26 ++++- .../DevServicesDatasourceProcessor.java | 109 ++++++++++-------- .../deployment/DevServicesKafkaProcessor.java | 34 ++++-- .../deployment/DevServicesMongoProcessor.java | 23 +++- .../KeycloakDevServicesProcessor.java | 93 ++++++++------- .../deployment/DevServicesRedisProcessor.java | 38 ++++-- .../deployment/AmqpDevServicesProcessor.java | 52 ++++++--- .../deployment/DevServicesVaultProcessor.java | 23 +++- .../test/junit/QuarkusMainTestExtension.java | 5 +- 16 files changed, 410 insertions(+), 172 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleInstalledBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/console/StartupLogCompressor.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java b/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java index e876e9a9b5b65..d438f50734ba0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java @@ -55,6 +55,7 @@ protected Boolean initialValue() { static final TreeMap statusMap = new TreeMap<>(); private final ReadWriteLock positionLock = new ReentrantReadWriteLock(); private volatile boolean closed; + private final StatusLine prompt; public AeshConsole(Connection connection) { INSTANCE = this; @@ -67,6 +68,7 @@ public void run() { connection.close(); } }, "Console Shutdown Hook")); + prompt = registerStatusLine(0); } private void updatePromptOnChange(StringBuilder buffer, int newLines) { @@ -109,7 +111,7 @@ public StatusLine registerStatusLine(int priority) { @Override public void setPromptMessage(String promptMessage) { - setMessage(0, promptMessage); + prompt.setMessage(promptMessage); } private AeshConsole setMessage(int position, String message) { @@ -386,6 +388,11 @@ public void write(boolean errorStream, byte[] buf, int off, int len) { write(errorStream, new String(buf, off, len, connection.outputEncoding())); } + @Override + public boolean isAnsiSupported() { + return true; + } + @Override public void doReadLine() { setPromptMessage(""); @@ -395,10 +402,13 @@ public void doReadLine() { } void rebalance() { - int count = 1; - for (var val : statusMap.values()) { - val.position = count; - setMessage(count++, val.message); + synchronized (this) { + int count = 1; + messages = new String[statusMap.size()]; + for (var val : statusMap.values()) { + val.position = count; + setMessage(count++, val.message); + } } } @@ -407,6 +417,7 @@ class StatusLineImpl implements StatusLine { final int priority; int position; String message; + boolean closed; StatusLineImpl(int priority) { this.priority = priority; @@ -414,10 +425,12 @@ class StatusLineImpl implements StatusLine { @Override public void setMessage(String message) { - this.message = message; try { positionLock.readLock().lock(); - AeshConsole.this.setMessage(position, message); + this.message = message; + if (!closed) { + AeshConsole.this.setMessage(position, message); + } } finally { positionLock.readLock().unlock(); } @@ -426,6 +439,7 @@ public void setMessage(String message) { @Override public void close() { positionLock.writeLock().lock(); + closed = true; try { AeshConsole.this.setMessage(position, null); statusMap.remove(priority); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleHelper.java b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleHelper.java index e568b2d7f91ff..b9710dc97a47a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleHelper.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleHelper.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.console; import java.io.IOException; -import java.io.PrintStream; import java.util.concurrent.LinkedBlockingDeque; import java.util.function.Consumer; import java.util.function.Supplier; @@ -12,30 +11,11 @@ import io.quarkus.deployment.dev.testing.TestConfig; import io.quarkus.dev.console.BasicConsole; import io.quarkus.dev.console.QuarkusConsole; -import io.quarkus.dev.console.RedirectPrintStream; import io.quarkus.runtime.console.ConsoleRuntimeConfig; import io.quarkus.runtime.util.ColorSupport; public class ConsoleHelper { - static boolean redirectsInstalled = false; - - final static PrintStream out = System.out; - final static PrintStream err = System.err; - - public synchronized static void installRedirects() { - if (redirectsInstalled) { - return; - } - redirectsInstalled = true; - - //force console init - //otherwise you can get a stack overflow as it sees the redirected output - QuarkusConsole.INSTANCE.isInputSupported(); - System.setOut(new RedirectPrintStream(false)); - System.setErr(new RedirectPrintStream(true)); - } - public static synchronized void installConsole(TestConfig config, ConsoleConfig consoleConfig, ConsoleRuntimeConfig consoleRuntimeConfig, io.quarkus.runtime.logging.ConsoleConfig logConfig, boolean test) { if (QuarkusConsole.installed) { @@ -50,7 +30,7 @@ public static synchronized void installConsole(TestConfig config, ConsoleConfig if (!inputSupport) { //note that in this case we don't hold onto anything from this class loader //which is important for the test suite - QuarkusConsole.INSTANCE = new BasicConsole(colorEnabled, false, out, System.console()); + QuarkusConsole.INSTANCE = new BasicConsole(colorEnabled, false, QuarkusConsole.ORIGINAL_OUT, System.console()); return; } try { @@ -109,8 +89,8 @@ public Integer get() { } }); } catch (IOException e) { - QuarkusConsole.INSTANCE = new BasicConsole(colorEnabled, false, out, System.console()); + QuarkusConsole.INSTANCE = new BasicConsole(colorEnabled, false, QuarkusConsole.ORIGINAL_OUT, System.console()); } - installRedirects(); + QuarkusConsole.installRedirects(); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleInstalledBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleInstalledBuildItem.java new file mode 100644 index 0000000000000..b3fdeb7f59bfd --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleInstalledBuildItem.java @@ -0,0 +1,13 @@ +package io.quarkus.deployment.console; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Build item that signifies that the interactive console is ready. + * + * This will not always be present, as the console may not be installed + */ +public final class ConsoleInstalledBuildItem extends SimpleBuildItem { + + public static final ConsoleInstalledBuildItem INSTANCE = new ConsoleInstalledBuildItem(); +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java index 4b98a065a324f..3da8b313819c6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java @@ -27,11 +27,12 @@ public class ConsoleProcessor { */ @BuildStep(onlyIf = IsDevelopment.class) @Produce(TestSetupBuildItem.class) - void setupConsole(TestConfig config, BuildProducer testListenerBuildItemBuildProducer, + ConsoleInstalledBuildItem setupConsole(TestConfig config, + BuildProducer testListenerBuildItemBuildProducer, LaunchModeBuildItem launchModeBuildItem, ConsoleConfig consoleConfig) { if (consoleInstalled) { - return; + return ConsoleInstalledBuildItem.INSTANCE; } consoleInstalled = true; if (config.console.orElse(consoleConfig.enabled)) { @@ -49,11 +50,12 @@ void setupConsole(TestConfig config, BuildProducer testLi //note that this bit needs to be refactored so it is no longer tied to continuous testing if (!TestSupport.instance().isPresent() || config.continuousTesting == TestConfig.Mode.DISABLED || config.flatClassPath) { - return; + return ConsoleInstalledBuildItem.INSTANCE; } TestConsoleHandler consoleHandler = new TestConsoleHandler(launchModeBuildItem.getDevModeType().get()); consoleHandler.install(); testListenerBuildItemBuildProducer.produce(new TestListenerBuildItem(consoleHandler)); } + return ConsoleInstalledBuildItem.INSTANCE; } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/StartupLogCompressor.java b/core/deployment/src/main/java/io/quarkus/deployment/console/StartupLogCompressor.java new file mode 100644 index 0000000000000..24d163e547c02 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/StartupLogCompressor.java @@ -0,0 +1,79 @@ +package io.quarkus.deployment.console; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiPredicate; + +import io.quarkus.deployment.dev.testing.MessageFormat; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; +import io.quarkus.dev.console.QuarkusConsole; +import io.quarkus.dev.console.StatusLine; + +/** + * special filter that can be used to compress log messages to a status line + *

+ * This is useful for Dev Services to show progress without cluttering up the logs + */ +public class StartupLogCompressor implements Closeable, BiPredicate { + + final Thread thread; + final String name; + final StatusLine sl; + final List toDump = new ArrayList<>(); + final AtomicInteger COUNTER = new AtomicInteger(); + + public StartupLogCompressor(String name, + @SuppressWarnings("unused") Optional consoleInstalledBuildItem, + @SuppressWarnings("unused") LoggingSetupBuildItem loggingSetupBuildItem) { + if (QuarkusConsole.INSTANCE.isAnsiSupported()) { + QuarkusConsole.installRedirects(); + this.name = name; + this.thread = Thread.currentThread(); + QuarkusConsole.addOutputFilter(this); + sl = QuarkusConsole.INSTANCE.registerStatusLine(1000 + COUNTER.incrementAndGet()); + sl.setMessage(MessageFormat.BLUE + name + MessageFormat.RESET); + } else { + thread = null; + this.name = null; + sl = null; + } + } + + @Override + public void close() { + if (thread == null) { + return; + } + QuarkusConsole.removeOutputFilter(this); + sl.close(); + } + + public void closeAndDumpCaptured() { + if (thread == null) { + return; + } + QuarkusConsole.removeOutputFilter(this); + sl.close(); + for (var i : toDump) { + QuarkusConsole.INSTANCE.write(true, i); + } + } + + @Override + public boolean test(String s, Boolean errorStream) { + if (thread == null) { + //not installed + return true; + } + if (Thread.currentThread() == thread) { + toDump.add(s); + sl.setMessage(MessageFormat.BLUE + name + MessageFormat.RESET + " " + s.replace("\n", "")); + return false; + } + return true; + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java index 7e7b66e13853d..3987951da4a21 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java @@ -198,7 +198,7 @@ public FilterResult apply(TestDescriptor testDescriptor) { listener.runStarted(toRun); } log.debug("Starting test run with " + testPlan.countTestIdentifiers((s) -> true) + " tests"); - QuarkusConsole.INSTANCE.addOutputFilter(logHandler); + QuarkusConsole.addOutputFilter(logHandler); final Deque> touchedClasses = new LinkedBlockingDeque<>(); Map startTimes = new HashMap<>(); diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java b/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java index f2655d4329ed6..8e12135244a3a 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java @@ -1,5 +1,6 @@ package io.quarkus.dev.console; +import java.io.PrintStream; import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; @@ -45,6 +46,24 @@ public abstract class QuarkusConsole { private volatile boolean started = false; + static boolean redirectsInstalled = false; + + public final static PrintStream ORIGINAL_OUT = System.out; + public final static PrintStream ORIGINAL_ERR = System.err; + + public synchronized static void installRedirects() { + if (redirectsInstalled) { + return; + } + redirectsInstalled = true; + + //force console init + //otherwise you can get a stack overflow as it sees the redirected output + QuarkusConsole.INSTANCE.isInputSupported(); + System.setOut(new RedirectPrintStream(false)); + System.setErr(new RedirectPrintStream(true)); + } + public static boolean hasColorSupport() { if (Boolean.getBoolean(FORCE_COLOR_SUPPORT)) { return true; //assume the IDE run window has color support @@ -120,4 +139,8 @@ protected boolean shouldWrite(boolean errorStream, String s) { public boolean isInputSupported() { return true; } + + public boolean isAnsiSupported() { + return false; + } } diff --git a/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java b/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java index 1147572ecc9bf..b644e4a860a0a 100644 --- a/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java +++ b/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java @@ -20,7 +20,10 @@ import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.devservices.common.ContainerLocator; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ConfigUtils; @@ -57,7 +60,9 @@ public class DevServicesApicurioRegistryProcessor { public void startApicurioRegistryDevService(LaunchModeBuildItem launchMode, ApicurioRegistryDevServicesBuildTimeConfig apicurioRegistryDevServices, Optional devServicesSharedNetworkBuildItem, - BuildProducer devServicesConfiguration) { + BuildProducer devServicesConfiguration, + Optional consoleInstalledBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem) { ApicurioRegistryDevServiceCfg configuration = getConfiguration(apicurioRegistryDevServices); @@ -69,11 +74,20 @@ public void startApicurioRegistryDevService(LaunchModeBuildItem launchMode, shutdownApicurioRegistry(); cfg = null; } - - ApicurioRegistry apicurioRegistry = startApicurioRegistry(configuration, launchMode, - devServicesSharedNetworkBuildItem.isPresent()); - if (apicurioRegistry == null) { - return; + ApicurioRegistry apicurioRegistry; + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Apicurio Registry Dev Services Starting:", + consoleInstalledBuildItem, loggingSetupBuildItem); + try { + apicurioRegistry = startApicurioRegistry(configuration, launchMode, + devServicesSharedNetworkBuildItem.isPresent()); + if (apicurioRegistry == null) { + return; + } + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); } cfg = configuration; diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java index 0881c79369314..537dbbec2327c 100644 --- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java @@ -27,7 +27,10 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.runtime.LaunchMode; @@ -50,7 +53,9 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura DataSourcesBuildTimeConfig dataSourceBuildTimeConfig, LaunchModeBuildItem launchMode, List configurationHandlerBuildItems, - BuildProducer devServicesResultBuildItemBuildProducer) { + BuildProducer devServicesResultBuildItemBuildProducer, + Optional consoleInstalledBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem) { //figure out if we need to shut down and restart existing databases //if not and the DB's have already started we just return if (databases != null) { @@ -111,60 +116,70 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura Map devDBProviderMap = devDBProviders.stream() .collect(Collectors.toMap(DevServicesDatasourceProviderBuildItem::getDatabase, DevServicesDatasourceProviderBuildItem::getDevServicesProvider)); - - defaultResult = startDevDb(null, curateOutcomeBuildItem, installedDrivers, - !dataSourceBuildTimeConfig.namedDataSources.isEmpty(), - devDBProviderMap, - dataSourceBuildTimeConfig.defaultDataSource, - configHandlersByDbType, propertiesMap, closeableList, launchMode.getLaunchMode()); - List dbConfig = new ArrayList<>(); - if (defaultResult != null) { - for (Map.Entry i : defaultResult.getConfigProperties().entrySet()) { - dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue())); - } - } - for (Map.Entry entry : dataSourceBuildTimeConfig.namedDataSources.entrySet()) { - DevServicesDatasourceResultBuildItem.DbResult result = startDevDb(entry.getKey(), curateOutcomeBuildItem, - installedDrivers, true, - devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, closeableList, - launchMode.getLaunchMode()); - if (result != null) { - namedResults.put(entry.getKey(), result); - for (Map.Entry i : result.getConfigProperties().entrySet()) { + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Database Starting:", + consoleInstalledBuildItem, + loggingSetupBuildItem); + try { + defaultResult = startDevDb(null, curateOutcomeBuildItem, installedDrivers, + !dataSourceBuildTimeConfig.namedDataSources.isEmpty(), + devDBProviderMap, + dataSourceBuildTimeConfig.defaultDataSource, + configHandlersByDbType, propertiesMap, closeableList, launchMode.getLaunchMode()); + List dbConfig = new ArrayList<>(); + if (defaultResult != null) { + for (Map.Entry i : defaultResult.getConfigProperties().entrySet()) { dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue())); } } - } - for (DevServicesConfigResultBuildItem i : dbConfig) { - devServicesResultBuildItemBuildProducer - .produce(i); - } + for (Map.Entry entry : dataSourceBuildTimeConfig.namedDataSources.entrySet()) { + DevServicesDatasourceResultBuildItem.DbResult result = startDevDb(entry.getKey(), curateOutcomeBuildItem, + installedDrivers, true, + devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, closeableList, + launchMode.getLaunchMode()); + if (result != null) { + namedResults.put(entry.getKey(), result); + for (Map.Entry i : result.getConfigProperties().entrySet()) { + dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue())); + } + } + } + for (DevServicesConfigResultBuildItem i : dbConfig) { + devServicesResultBuildItemBuildProducer + .produce(i); + } - if (first) { - first = false; - Runnable closeTask = new Runnable() { - @Override - public void run() { - if (databases != null) { - for (Closeable i : databases) { - try { - i.close(); - } catch (Throwable t) { - log.error("Failed to stop database", t); + if (first) { + first = false; + Runnable closeTask = new Runnable() { + @Override + public void run() { + if (databases != null) { + for (Closeable i : databases) { + try { + i.close(); + } catch (Throwable t) { + log.error("Failed to stop database", t); + } } } + first = true; + databases = null; + cachedProperties = null; } - first = true; - databases = null; - cachedProperties = null; - } - }; - QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); - ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask); + }; + QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); + ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask); + } + databases = closeableList; + cachedProperties = propertiesMap; + compressor.close(); + log.info("Dev Services for datasources started."); + return new DevServicesDatasourceResultBuildItem(defaultResult, namedResults); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); } - databases = closeableList; - cachedProperties = propertiesMap; - return new DevServicesDatasourceResultBuildItem(defaultResult, namedResults); } private DevServicesDatasourceResultBuildItem.DbResult startDevDb(String dbName, diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java index 25d1eb9a06cc7..8b72496eb8ec7 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java @@ -42,7 +42,10 @@ import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.devservices.common.ContainerAddress; import io.quarkus.devservices.common.ContainerLocator; import io.quarkus.runtime.LaunchMode; @@ -76,7 +79,9 @@ public DevServicesKafkaBrokerBuildItem startKafkaDevService( LaunchModeBuildItem launchMode, KafkaBuildTimeConfig kafkaClientBuildTimeConfig, Optional devServicesSharedNetworkBuildItem, - BuildProducer devServicePropertiesProducer) { + BuildProducer devServicePropertiesProducer, + Optional consoleInstalledBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem) { KafkaDevServiceCfg configuration = getConfiguration(kafkaClientBuildTimeConfig); @@ -88,14 +93,25 @@ public DevServicesKafkaBrokerBuildItem startKafkaDevService( shutdownBroker(); cfg = null; } - - KafkaBroker kafkaBroker = startKafka(configuration, launchMode, devServicesSharedNetworkBuildItem.isPresent()); - DevServicesKafkaBrokerBuildItem bootstrapServers = null; - if (kafkaBroker != null) { - closeable = kafkaBroker.getCloseable(); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem( - KAFKA_BOOTSTRAP_SERVERS, kafkaBroker.getBootstrapServers())); - bootstrapServers = new DevServicesKafkaBrokerBuildItem(kafkaBroker.getBootstrapServers()); + KafkaBroker kafkaBroker; + DevServicesKafkaBrokerBuildItem bootstrapServers; + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Kafka Dev Services Starting:", + consoleInstalledBuildItem, loggingSetupBuildItem); + try { + + kafkaBroker = startKafka(configuration, launchMode, devServicesSharedNetworkBuildItem.isPresent()); + bootstrapServers = null; + if (kafkaBroker != null) { + closeable = kafkaBroker.getCloseable(); + devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem( + KAFKA_BOOTSTRAP_SERVERS, kafkaBroker.getBootstrapServers())); + bootstrapServers = new DevServicesKafkaBrokerBuildItem(kafkaBroker.getBootstrapServers()); + } + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); } // Configure the watch dog diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java index 1564f2fbbf38f..1f49d9c9bca88 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java @@ -27,7 +27,11 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.devservices.common.ConfigureUtil; import io.quarkus.mongodb.runtime.MongodbConfig; import io.quarkus.runtime.configuration.ConfigUtils; @@ -46,7 +50,10 @@ public class DevServicesMongoProcessor { public void startMongo(List mongoConnections, MongoClientBuildTimeConfig mongoClientBuildTimeConfig, Optional devServicesSharedNetworkBuildItem, - BuildProducer devServices) { + BuildProducer devServices, + Optional consoleInstalledBuildItem, + LaunchModeBuildItem launchMode, + LoggingSetupBuildItem loggingSetupBuildItem) { List connectionNames = new ArrayList<>(mongoConnections.size()); for (MongoConnectionNameBuildItem mongoConnection : mongoConnections) { @@ -86,8 +93,18 @@ public void startMongo(List mongoConnections, // TODO: we need to go through each connection String connectionName = connectionNames.get(0); - StartResult startResult = startMongo(connectionName, currentCapturedProperties.get(connectionName), - devServicesSharedNetworkBuildItem.isPresent()); + StartResult startResult; + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Mongo Dev Services Starting:", consoleInstalledBuildItem, + loggingSetupBuildItem); + try { + startResult = startMongo(connectionName, currentCapturedProperties.get(connectionName), + devServicesSharedNetworkBuildItem.isPresent()); + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); + } if (startResult != null) { currentCloseables.add(startResult.getCloseable()); String connectionStringPropertyName = getConfigPrefix(connectionName) + "connection-string"; diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index 5bfc3126ebb1c..dd0d95877624b 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -45,7 +45,11 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.devservices.common.ContainerAddress; import io.quarkus.devservices.common.ContainerLocator; import io.quarkus.oidc.deployment.OidcBuildStep.IsEnabled; @@ -106,7 +110,10 @@ public KeycloakDevServicesConfigBuildItem startKeycloakContainer( Optional devServicesSharedNetworkBuildItem, BuildProducer devServices, Optional oidcProviderBuildItem, - KeycloakBuildTimeConfig config) { + KeycloakBuildTimeConfig config, + LaunchModeBuildItem launchMode, + Optional consoleInstalledBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem) { if (oidcProviderBuildItem.isPresent()) { // Dev Services for the alternative OIDC provider are enabled @@ -143,52 +150,60 @@ public KeycloakDevServicesConfigBuildItem startKeycloakContainer( existingDevServiceConfig = null; } capturedDevServicesConfiguration = currentDevServicesConfiguration; + StartResult startResult; + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "KeyCloak Dev Services Starting:", + consoleInstalledBuildItem, loggingSetupBuildItem); + try { + startResult = startContainer(devServicesSharedNetworkBuildItem.isPresent()); + if (startResult == null) { + return null; + } - StartResult startResult = startContainer(devServicesSharedNetworkBuildItem.isPresent()); - if (startResult == null) { - return null; - } - - closeables = startResult.closeable != null ? Collections.singletonList(startResult.closeable) : null; - - if (first) { - first = false; - Runnable closeTask = new Runnable() { - @Override - public void run() { - if (closeables != null) { - for (Closeable closeable : closeables) { + closeables = startResult.closeable != null ? Collections.singletonList(startResult.closeable) : null; + + if (first) { + first = false; + Runnable closeTask = new Runnable() { + @Override + public void run() { + if (closeables != null) { + for (Closeable closeable : closeables) { + try { + closeable.close(); + } catch (Throwable t) { + LOG.error("Failed to stop Keycloak container", t); + } + } + } + if (vertxInstance != null) { try { - closeable.close(); + vertxInstance.close(); } catch (Throwable t) { - LOG.error("Failed to stop Keycloak container", t); + LOG.error("Failed to close Vertx instance", t); } } + first = true; + closeables = null; + capturedDevServicesConfiguration = null; + vertxInstance = null; + capturedRealmFileLastModifiedDate = null; } - if (vertxInstance != null) { - try { - vertxInstance.close(); - } catch (Throwable t) { - LOG.error("Failed to close Vertx instance", t); - } - } - first = true; - closeables = null; - capturedDevServicesConfiguration = null; - vertxInstance = null; - capturedRealmFileLastModifiedDate = null; - } - }; - QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); - ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask); - } + }; + QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); + ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask); + } - capturedKeycloakUrl = startResult.url + "/auth"; - if (vertxInstance == null) { - vertxInstance = Vertx.vertx(); + capturedKeycloakUrl = startResult.url + "/auth"; + if (vertxInstance == null) { + vertxInstance = Vertx.vertx(); + } + capturedRealmFileLastModifiedDate = getRealmFileLastModifiedDate(capturedDevServicesConfiguration.realmPath); + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); } - capturedRealmFileLastModifiedDate = getRealmFileLastModifiedDate(capturedDevServicesConfiguration.realmPath); - LOG.info("Dev Services for Keycloak started."); return prepareConfiguration(capturedDevServicesConfiguration.createRealm && startResult.createDefaultRealm, diff --git a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java index f0edb1dc23934..e1d0cda173e61 100644 --- a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java +++ b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java @@ -28,7 +28,10 @@ import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.devservices.common.ContainerLocator; import io.quarkus.redis.client.deployment.RedisBuildTimeConfig.DevServiceConfiguration; import io.quarkus.redis.client.runtime.RedisClientUtil; @@ -60,7 +63,9 @@ public class DevServicesRedisProcessor { @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class }) public void startRedisContainers(LaunchModeBuildItem launchMode, Optional devServicesSharedNetworkBuildItem, - BuildProducer devConfigProducer, RedisBuildTimeConfig config) { + BuildProducer devConfigProducer, RedisBuildTimeConfig config, + Optional consoleInstalledBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem) { Map currentDevServicesConfiguration = new HashMap<>(config.additionalDevServices); currentDevServicesConfiguration.put(RedisClientUtil.DEFAULT_CLIENT, config.defaultDevService); @@ -85,17 +90,28 @@ public void startRedisContainers(LaunchModeBuildItem launchMode, capturedDevServicesConfiguration = currentDevServicesConfiguration; List currentCloseables = new ArrayList<>(); - for (Entry entry : currentDevServicesConfiguration.entrySet()) { - String connectionName = entry.getKey(); - StartResult startResult = startContainer(connectionName, entry.getValue().devservices, launchMode.getLaunchMode(), - devServicesSharedNetworkBuildItem.isPresent()); - if (startResult == null) { - continue; + + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Redis Dev Services Starting:", consoleInstalledBuildItem, + loggingSetupBuildItem); + try { + for (Entry entry : currentDevServicesConfiguration.entrySet()) { + String connectionName = entry.getKey(); + StartResult startResult = startContainer(connectionName, entry.getValue().devservices, + launchMode.getLaunchMode(), + devServicesSharedNetworkBuildItem.isPresent()); + if (startResult == null) { + continue; + } + currentCloseables.add(startResult.closeable); + String configKey = getConfigPrefix(connectionName) + RedisConfig.HOSTS_CONFIG_NAME; + devConfigProducer.produce(new DevServicesConfigResultBuildItem(configKey, startResult.url)); + log.infof("The %s redis server is ready to accept connections on %s", connectionName, startResult.url); } - currentCloseables.add(startResult.closeable); - String configKey = getConfigPrefix(connectionName) + RedisConfig.HOSTS_CONFIG_NAME; - devConfigProducer.produce(new DevServicesConfigResultBuildItem(configKey, startResult.url)); - log.infof("The %s redis server is ready to accept connections on %s", connectionName, startResult.url); + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); } closeables = currentCloseables; diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java b/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java index ca48170751f0f..35483f64c4094 100644 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java +++ b/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java @@ -2,6 +2,7 @@ import java.io.Closeable; import java.util.Objects; +import java.util.Optional; import java.util.function.Supplier; import org.eclipse.microprofile.config.Config; @@ -19,7 +20,10 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.devservices.common.ContainerLocator; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ConfigUtils; @@ -60,7 +64,9 @@ public class AmqpDevServicesProcessor { public DevServicesAmqpBrokerBuildItem startAmqpDevService( LaunchModeBuildItem launchMode, AmqpBuildTimeConfig amqpClientBuildTimeConfig, - BuildProducer devServicePropertiesProducer) { + BuildProducer devServicePropertiesProducer, + Optional consoleInstalledBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem) { AmqpDevServiceCfg configuration = getConfiguration(amqpClientBuildTimeConfig); @@ -73,25 +79,35 @@ public DevServicesAmqpBrokerBuildItem startAmqpDevService( cfg = null; } - AmqpBroker broker = startAmqpBroker(configuration, launchMode); + AmqpBroker broker; DevServicesAmqpBrokerBuildItem artemis = null; - if (broker != null) { - closeable = broker.getCloseable(); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_HOST_PROP, broker.host)); - devServicePropertiesProducer - .produce(new DevServicesConfigResultBuildItem(AMQP_PORT_PROP, Integer.toString(broker.port))); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_USER_PROP, broker.user)); - devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_PASSWORD_PROP, broker.password)); - - artemis = new DevServicesAmqpBrokerBuildItem(broker.host, broker.port, broker.user, broker.password); - - if (broker.isOwner()) { - log.info("Dev Services for AMQP started."); - log.infof("Other Quarkus applications in dev mode will find the " - + "broker automatically. For Quarkus applications in production mode, you can connect to" - + " this by starting your application with -Damqp.host=%s -Damqp.port=%d -Damqp.user=%s -Damqp.password=%s", - broker.host, broker.port, broker.user, broker.password); + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "AMQP Dev Services Starting:", consoleInstalledBuildItem, + loggingSetupBuildItem); + try { + broker = startAmqpBroker(configuration, launchMode); + if (broker != null) { + closeable = broker.getCloseable(); + devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_HOST_PROP, broker.host)); + devServicePropertiesProducer + .produce(new DevServicesConfigResultBuildItem(AMQP_PORT_PROP, Integer.toString(broker.port))); + devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_USER_PROP, broker.user)); + devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(AMQP_PASSWORD_PROP, broker.password)); + + artemis = new DevServicesAmqpBrokerBuildItem(broker.host, broker.port, broker.user, broker.password); + + if (broker.isOwner()) { + log.info("Dev Services for AMQP started."); + log.infof("Other Quarkus applications in dev mode will find the " + + "broker automatically. For Quarkus applications in production mode, you can connect to" + + " this by starting your application with -Damqp.host=%s -Damqp.port=%d -Damqp.user=%s -Damqp.password=%s", + broker.host, broker.port, broker.user, broker.password); + } } + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); } // Configure the watch dog diff --git a/extensions/vault/deployment/src/main/java/io/quarkus/vault/deployment/DevServicesVaultProcessor.java b/extensions/vault/deployment/src/main/java/io/quarkus/vault/deployment/DevServicesVaultProcessor.java index 0d897b7f31075..d65562542a50e 100644 --- a/extensions/vault/deployment/src/main/java/io/quarkus/vault/deployment/DevServicesVaultProcessor.java +++ b/extensions/vault/deployment/src/main/java/io/quarkus/vault/deployment/DevServicesVaultProcessor.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.OptionalInt; import org.apache.commons.lang3.RandomStringUtils; @@ -18,7 +19,11 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.runtime.configuration.ConfigUtils; import io.quarkus.vault.runtime.VaultVersions; import io.quarkus.vault.runtime.config.DevServicesConfig; @@ -38,7 +43,10 @@ public class DevServicesVaultProcessor { private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) - public void startVaultContainers(BuildProducer devConfig, VaultBuildTimeConfig config) { + public void startVaultContainers(BuildProducer devConfig, VaultBuildTimeConfig config, + Optional consoleInstalledBuildItem, + LaunchModeBuildItem launchMode, + LoggingSetupBuildItem loggingSetupBuildItem) { DevServicesConfig currentDevServicesConfiguration = config.devservices; @@ -62,7 +70,18 @@ public void startVaultContainers(BuildProducer capturedDevServicesConfiguration = currentDevServicesConfiguration; - StartResult startResult = startContainer(currentDevServicesConfiguration); + StartResult startResult; + + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Vault Dev Services Starting:", consoleInstalledBuildItem, + loggingSetupBuildItem); + try { + startResult = startContainer(currentDevServicesConfiguration); + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); + } if (startResult == null) { return; } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java index 8e4a50be2f79a..59d4bef6e7900 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java @@ -17,7 +17,6 @@ import org.junit.jupiter.api.extension.ParameterResolver; import io.quarkus.bootstrap.app.StartupAction; -import io.quarkus.deployment.console.ConsoleHelper; import io.quarkus.deployment.dev.testing.LogCapturingOutputFilter; import io.quarkus.dev.console.QuarkusConsole; import io.quarkus.dev.testing.TracingHandler; @@ -69,7 +68,7 @@ private void ensurePrepared(ExtensionContext context) throws Exception { private LaunchResult doLaunch(ExtensionContext context, String[] arguments) throws Exception { ensurePrepared(context); - ConsoleHelper.installRedirects(); + QuarkusConsole.installRedirects(); LogCapturingOutputFilter filter = new LogCapturingOutputFilter(prepareResult.curatedApplication, false, false, () -> true); QuarkusConsole.addOutputFilter(filter); @@ -182,7 +181,7 @@ public void afterAll(ExtensionContext context) throws Exception { @Override public void beforeAll(ExtensionContext context) throws Exception { - ConsoleHelper.installRedirects(); + QuarkusConsole.installRedirects(); currentTestClassStack.push(context.getRequiredTestClass()); }