This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new d503e06ffd76 CAMEL-22748: camel-jbang - camel debug to do remote
attach to existing running Camel (#20233)
d503e06ffd76 is described below
commit d503e06ffd76752fd408e460c6b83dbe606bfa9d
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Dec 4 18:44:31 2025 +0100
CAMEL-22748: camel-jbang - camel debug to do remote attach to existing
running Camel (#20233)
---
bom/camel-bom/pom.xml | 5 +
.../org/apache/camel/catalog/others.properties | 1 +
.../org/apache/camel/catalog/others/cli-debug.json | 15 +++
.../java/org/apache/camel/spi/CliConnector.java | 7 ++
.../org/apache/camel/spi/ShutdownPrepared.java | 6 +-
.../impl/debugger/DefaultBacklogDebugger.java | 12 +-
.../camel/impl/engine/AbstractCamelContext.java | 23 ++--
docs/components/modules/others/nav.adoc | 1 +
.../components/modules/others/pages/cli-debug.adoc | 1 +
.../camel/cli/connector/LocalCliConnector.java | 50 +++++++-
dsl/{ => camel-cli-debug}/pom.xml | 61 ++++------
.../services/org/apache/camel/debugger-factory | 2 +
.../services/org/apache/camel/other.properties | 7 ++
.../src/generated/resources/cli-debug.json | 15 +++
dsl/camel-cli-debug/src/main/docs/cli-debug.adoc | 55 +++++++++
.../cli/debug/CamelCliDebuggerFactory.java | 127 +++++++++++++++++++++
.../camel/dsl/jbang/core/commands/Debug.java | 119 ++++++++++++++++++-
dsl/pom.xml | 1 +
parent/pom.xml | 5 +
19 files changed, 451 insertions(+), 62 deletions(-)
diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml
index 10a4c83ec8eb..710d1a661cba 100644
--- a/bom/camel-bom/pom.xml
+++ b/bom/camel-bom/pom.xml
@@ -441,6 +441,11 @@
<artifactId>camel-cli-connector</artifactId>
<version>4.17.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-cli-debug</artifactId>
+ <version>4.17.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-clickup</artifactId>
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
index ed5acbb0859e..964ecb3fc0d7 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
@@ -2,6 +2,7 @@ attachments
aws-xray
azure-schema-registry
cli-connector
+cli-debug
cloud
cloudevents
cluster
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/cli-debug.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/cli-debug.json
new file mode 100644
index 000000000000..4acfc083d275
--- /dev/null
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/cli-debug.json
@@ -0,0 +1,15 @@
+{
+ "other": {
+ "kind": "other",
+ "name": "cli-debug",
+ "title": "CLI Debug",
+ "description": "Remote CLI debugger",
+ "deprecated": false,
+ "firstVersion": "4.17.0",
+ "label": "tooling",
+ "supportLevel": "Preview",
+ "groupId": "org.apache.camel",
+ "artifactId": "camel-cli-debug",
+ "version": "4.17.0-SNAPSHOT"
+ }
+}
diff --git
a/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java
b/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java
index ded99a33e065..36997762846b 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java
@@ -31,4 +31,11 @@ public interface CliConnector extends StaticService,
NonManagedService {
*/
void sigterm();
+ /**
+ * Allows to adjust the frequency delay which the CliConnect reacts. Uses
1000 millis by default.
+ *
+ * @param delay delay in millis
+ */
+ void updateDelay(int delay);
+
}
diff --git
a/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java
b/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java
index 60f2c8c246e0..666dc38abdcf 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java
@@ -37,7 +37,7 @@ public interface ShutdownPrepared {
* {@link ShutdownStrategy} performs a more aggressive shutdown, calling
this method a second time with
* <tt>true</tt> for the given forced parameter. For example by graceful
stopping any threads or the likes.
* <p/>
- * In addition a service can also be suspended (not stopped), and when
this happens the parameter
+ * In addition, a service can also be suspended (not stopped), and when
this happens the parameter
* <tt>suspendOnly</tt> has the value <tt>true</tt>. This can be used to
prepare the service for suspension, such as
* marking a worker thread to skip action.
* <p/>
@@ -46,8 +46,8 @@ public interface ShutdownPrepared {
*
* @param suspendOnly <tt>true</tt> if the intention is to only suspend
the service, and not stop/shutdown the
* service.
- * @param forced <tt>true</tt> is forcing a more aggressive shutdown,
<tt>false</tt> is for preparing to
- * shutdown.
+ * @param forced <tt>true</tt> is forcing a more aggressive shutdown,
<tt>false</tt> is for preparing to shut
+ * down.
*/
void prepareShutdown(boolean suspendOnly, boolean forced);
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
index 2d41f81a70a4..92daba25a46d 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
@@ -50,6 +50,7 @@ import org.apache.camel.spi.CamelEvent.ExchangeEvent;
import org.apache.camel.spi.CamelLogger;
import org.apache.camel.spi.Condition;
import org.apache.camel.spi.Debugger;
+import org.apache.camel.spi.ShutdownPrepared;
import org.apache.camel.support.BreakpointSupport;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.LoggerHelper;
@@ -63,7 +64,7 @@ import org.apache.camel.util.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public final class DefaultBacklogDebugger extends ServiceSupport implements
BacklogDebugger {
+public final class DefaultBacklogDebugger extends ServiceSupport implements
BacklogDebugger, ShutdownPrepared {
private static final Logger LOG =
LoggerFactory.getLogger(DefaultBacklogDebugger.class);
@@ -267,6 +268,15 @@ public final class DefaultBacklogDebugger extends
ServiceSupport implements Back
}
}
+ @Override
+ public void prepareShutdown(boolean suspendOnly, boolean forced) {
+ logger.log("Preparing debugger for shutdown");
+ // camel is being shutdown so if we are suspended during debugging
+ // then turn this off so messages can finish processing
+ suspendMode = false;
+ resumeMessageProcessing();
+ }
+
/**
* Resolves the value of the flag indicating whether the {@code
BacklogDebugger} should suspend processing the
* messages and wait for a debugger to attach or not.
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index a321429a24d7..73c7e1165023 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -2506,6 +2506,18 @@ public abstract class AbstractCamelContext extends
BaseService
forceLazyInitialization();
+ // setup cli-connector if not already done (before debugger)
+ if (hasService(CliConnector.class) == null) {
+ CliConnectorFactory ccf =
getCamelContextExtension().getContextPlugin(CliConnectorFactory.class);
+ if (ccf != null && ccf.isEnabled()) {
+ CliConnector connector = ccf.createConnector();
+ addService(connector, true);
+ // force start cli connector early as otherwise it will be
deferred until context is started
+ // but, we want status available during startup phase
+ ServiceHelper.startService(connector);
+ }
+ }
+
// auto-detect camel-debug on classpath (if debugger has not been
explicit added)
boolean debuggerDetected = false;
if (getDebugger() == null && hasService(BacklogDebugger.class) ==
null) {
@@ -2530,17 +2542,6 @@ public abstract class AbstractCamelContext extends
BaseService
addService(backlog, true, true);
}
}
- // setup cli-connector if not already done (after debugger)
- if (hasService(CliConnector.class) == null) {
- CliConnectorFactory ccf =
getCamelContextExtension().getContextPlugin(CliConnectorFactory.class);
- if (ccf != null && ccf.isEnabled()) {
- CliConnector connector = ccf.createConnector();
- addService(connector, true);
- // force start cli connector early as otherwise it will be
deferred until context is started
- // but, we want status available during startup phase
- ServiceHelper.startService(connector);
- }
- }
addService(getManagementStrategy(), false);
diff --git a/docs/components/modules/others/nav.adoc
b/docs/components/modules/others/nav.adoc
index 6ecb7d4919ce..dd4d8071bf88 100644
--- a/docs/components/modules/others/nav.adoc
+++ b/docs/components/modules/others/nav.adoc
@@ -6,6 +6,7 @@
** xref:aws-xray.adoc[AWS XRay]
** xref:azure-schema-registry.adoc[Azure Schema Registry]
** xref:cli-connector.adoc[CLI Connector]
+** xref:cli-debug.adoc[CLI Debug]
** xref:cloudevents.adoc[Cloudevents]
** xref:csimple-joor.adoc[CSimple jOOR]
** xref:cxf-transport.adoc[CXF Transport]
diff --git a/docs/components/modules/others/pages/cli-debug.adoc
b/docs/components/modules/others/pages/cli-debug.adoc
new file mode 120000
index 000000000000..882fa9b85009
--- /dev/null
+++ b/docs/components/modules/others/pages/cli-debug.adoc
@@ -0,0 +1 @@
+../../../../../dsl/camel-cli-debug/src/main/docs/cli-debug.adoc
\ No newline at end of file
diff --git
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index 0e43279af66d..6019e7d817f0 100644
---
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -40,6 +40,7 @@ import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -63,6 +64,7 @@ import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.ProcessorDefinitionHelper;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.language.ExpressionDefinition;
+import org.apache.camel.spi.BacklogDebugger;
import org.apache.camel.spi.CliConnector;
import org.apache.camel.spi.CliConnectorFactory;
import org.apache.camel.spi.ContextReloadStrategy;
@@ -72,6 +74,7 @@ import org.apache.camel.spi.Resource;
import org.apache.camel.spi.ResourceLoader;
import org.apache.camel.spi.ResourceReloadStrategy;
import org.apache.camel.spi.RoutesLoader;
+import org.apache.camel.spi.ShutdownPrepared;
import org.apache.camel.support.LoadOnDemandReloadStrategy;
import org.apache.camel.support.MessageHelper;
import org.apache.camel.support.PatternHelper;
@@ -101,6 +104,7 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
private final CliConnectorFactory cliConnectorFactory;
private CamelContext camelContext;
+ private ScheduledFuture scheduledFuture;
private int delay = 1000;
private long counter;
private String platform;
@@ -192,13 +196,32 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
messageHistoryFile = createLockFile(lockFile.getName() +
"-history.json");
debugFile = createLockFile(lockFile.getName() + "-debug.json");
receiveFile = createLockFile(lockFile.getName() + "-receive.json");
- executor.scheduleWithFixedDelay(this::task, 0, delay,
TimeUnit.MILLISECONDS);
+ scheduledFuture = executor.scheduleWithFixedDelay(this::task, 0,
delay, TimeUnit.MILLISECONDS);
LOG.info("Camel JBang CLI enabled");
} else {
LOG.warn("Cannot create PID file: {}. This integration cannot be
managed by Camel JBang CLI.", getPid());
}
}
+ @Override
+ public void updateDelay(int delay) {
+ if (this.delay == delay) {
+ return;
+ }
+ if (scheduledFuture != null) {
+ try {
+ scheduledFuture.cancel(true);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ boolean done = scheduledFuture == null || scheduledFuture.isDone();
+ if (done) {
+ this.delay = delay;
+ scheduledFuture = executor.scheduleWithFixedDelay(this::task, 0,
delay, TimeUnit.MILLISECONDS);
+ }
+ }
+
@Override
public void sigterm() {
// we are terminating
@@ -214,6 +237,12 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
public void run() {
LOG.info("Camel JBang terminating JVM");
try {
+ // if we are debugging then detach before stopping camel
+ BacklogDebugger debugger =
camelContext.hasService(BacklogDebugger.class);
+ if (debugger instanceof ShutdownPrepared sp) {
+ sp.prepareShutdown(false, true);
+ }
+ ServiceHelper.stopAndShutdownServices(debugger);
camelContext.stop();
} finally {
ServiceHelper.stopAndShutdownService(this);
@@ -300,6 +329,8 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
doActionBrowseTask(root);
} else if ("receive".equals(action)) {
doActionReceiveTask(root);
+ } else if ("cli-debug".equals(action)) {
+ doActionCliDebug(root);
}
} catch (Exception e) {
// ignore
@@ -312,6 +343,23 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
}
}
+ private void doActionCliDebug(JsonObject root) {
+ String command = root.getString("command");
+ String breakpoint = root.getString("breakpoint");
+ BacklogDebugger debugger =
camelContext.hasService(BacklogDebugger.class);
+ if (debugger != null) {
+ if ("attach".equals(command)) {
+ if (breakpoint != null) {
+ debugger.setInitialBreakpoints(breakpoint);
+ }
+ debugger.enableDebugger();
+ debugger.attach();
+ } else if ("detach".equals(command)) {
+ debugger.detach();
+ }
+ }
+ }
+
private void doActionTransformTask(JsonObject root) throws Exception {
StopWatch watch = new StopWatch();
long timestamp = System.currentTimeMillis();
diff --git a/dsl/pom.xml b/dsl/camel-cli-debug/pom.xml
similarity index 64%
copy from dsl/pom.xml
copy to dsl/camel-cli-debug/pom.xml
index a3188d172db7..f4ae9f60b988 100644
--- a/dsl/pom.xml
+++ b/dsl/camel-cli-debug/pom.xml
@@ -17,51 +17,41 @@
limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.camel</groupId>
- <artifactId>camel-parent</artifactId>
+ <artifactId>dsl</artifactId>
<version>4.17.0-SNAPSHOT</version>
- <relativePath>../parent/pom.xml</relativePath>
</parent>
- <artifactId>dsl</artifactId>
- <packaging>pom</packaging>
-
- <name>Camel :: DSL :: Parent</name>
- <description>Camel DSL Parent</description>
+ <artifactId>camel-cli-debug</artifactId>
+ <packaging>jar</packaging>
+ <name>Camel :: DSL :: CLI Debug</name>
+ <description>Remote CLI debugger</description>
<properties>
- <camel.surefire.parallel>true</camel.surefire.parallel>
- <camel-prepare-component>true</camel-prepare-component>
+ <firstVersion>4.17.0</firstVersion>
+ <title>CLI Debug</title>
+ <label>tooling</label>
</properties>
- <modules>
- <module>camel-endpointdsl</module>
- <module>camel-componentdsl</module>
- <module>camel-cli-connector</module>
- <module>camel-dsl-support</module>
- <module>camel-dsl-modeline</module>
- <module>camel-endpointdsl-support</module>
- <module>camel-java-joor-dsl</module>
- <module>camel-xml-io-dsl</module>
- <module>camel-xml-jaxb-dsl</module>
- <module>camel-xml-jaxb-dsl-test</module>
- <module>camel-yaml-dsl</module>
- <module>camel-kamelet-main</module>
- <module>camel-jbang</module>
- </modules>
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-cli-connector</artifactId>
+ </dependency>
+
+ </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.camel</groupId>
<artifactId>camel-package-maven-plugin</artifactId>
- <configuration>
- </configuration>
-
<executions>
<execution>
<id>generate</id>
@@ -79,24 +69,12 @@
</execution>
</executions>
</plugin>
- <plugin>
- <artifactId>maven-compiler-plugin</artifactId>
- <executions>
- <execution>
- <id>recompile</id>
- <goals>
- <goal>compile</goal>
- </goals>
- <phase>process-classes</phase>
- </execution>
- </executions>
- </plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
- <phase>generate-sources</phase>
+ <phase>initialize</phase>
<goals>
<goal>add-source</goal>
<goal>add-resource</goal>
@@ -116,4 +94,5 @@
</plugin>
</plugins>
</build>
+
</project>
diff --git
a/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/debugger-factory
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/debugger-factory
new file mode 100644
index 000000000000..1247dbb94f9a
--- /dev/null
+++
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/debugger-factory
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.cli.debug.CamelCliDebuggerFactory
diff --git
a/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/other.properties
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/other.properties
new file mode 100644
index 000000000000..d6d2632c5751
--- /dev/null
+++
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/other.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+name=cli-debug
+groupId=org.apache.camel
+artifactId=camel-cli-debug
+version=4.17.0-SNAPSHOT
+projectName=Camel :: DSL :: CLI Debug
+projectDescription=Remote CLI debugger
diff --git a/dsl/camel-cli-debug/src/generated/resources/cli-debug.json
b/dsl/camel-cli-debug/src/generated/resources/cli-debug.json
new file mode 100644
index 000000000000..4acfc083d275
--- /dev/null
+++ b/dsl/camel-cli-debug/src/generated/resources/cli-debug.json
@@ -0,0 +1,15 @@
+{
+ "other": {
+ "kind": "other",
+ "name": "cli-debug",
+ "title": "CLI Debug",
+ "description": "Remote CLI debugger",
+ "deprecated": false,
+ "firstVersion": "4.17.0",
+ "label": "tooling",
+ "supportLevel": "Preview",
+ "groupId": "org.apache.camel",
+ "artifactId": "camel-cli-debug",
+ "version": "4.17.0-SNAPSHOT"
+ }
+}
diff --git a/dsl/camel-cli-debug/src/main/docs/cli-debug.adoc
b/dsl/camel-cli-debug/src/main/docs/cli-debug.adoc
new file mode 100644
index 000000000000..265d41de125d
--- /dev/null
+++ b/dsl/camel-cli-debug/src/main/docs/cli-debug.adoc
@@ -0,0 +1,55 @@
+= CLI Debug Component
+:doctitle: CLI Debug
+:shortname: cli-debug
+:artifactid: camel-cli-debug
+:description: Remote CLI debugger
+:since: 4.17
+:supportlevel: Preview
+:tabs-sync-option:
+//Manually maintained attributes
+:camel-spring-boot-name: debug
+
+*Since Camel {since}*
+
+The camel-cli-debug enables Camel debugger for Camel CLI (camel-jbang).
+
+[IMPORTANT]
+====
+The camel-cli-debug is only for development purposes, it should **not** be
used for production.
+
+Do not use both `camel-debug` and `camel-cli-debug` JARs in the Camel
application classpath. Use only `camel-debug` JAR.
+====
+
+== Auto-detection from classpath
+
+To use this implementation all you need to do is to add the `camel-cli-debug`
dependency to the classpath,
+and Camel should auto-detect this on startup and log as follows:
+
+[source,text]
+----
+Detected: camel-cli-debug JAR (Enabling Camel Debugging)
+----
+
+== Debugging
+
+Add `camel-cli-debug` JAR to the classpath of your application, and then run
this application. For example with Spring Boot using `mvn spring-boot:run`.
+
+Then the application starts and Camel detects the CLI debugger that then logs
a message, waiting for the CLI to remotely attach.
+
+From a CLI terminal then you can execute:
+
+[source,bash]
+----
+$ camel debug --remote-attach --name=<pid>
+----
+
+Where `<pid>` is the process id of the running Camel integration. You can see
this from the log such as:
+
+[source,text]
+----
+2025-12-04T15:48:12.542+01:00 INFO 6667 --- [ main]
o.a.c.impl.engine.AbstractCamelContext : Detected: camel-cli-debug JAR
(Enabling Camel Debugging)
+2025-12-04T15:48:12.543+01:00 INFO 6667 --- [ main]
o.a.c.c.c.debug.CamelCliDebuggerFactory :
================================================================================
+2025-12-04T15:48:12.543+01:00 INFO 6667 --- [ main]
o.a.c.c.c.debug.CamelCliDebuggerFactory : Waiting for CLI to remote attach
(camel debug --remote-attach --name=6667)
+----
+
+To connect the CLI to the Camel application that will then continue to start
and perform debugging from the CLI terminal.
\ No newline at end of file
diff --git
a/dsl/camel-cli-debug/src/main/java/org/apache/camel/component/cli/debug/CamelCliDebuggerFactory.java
b/dsl/camel-cli-debug/src/main/java/org/apache/camel/component/cli/debug/CamelCliDebuggerFactory.java
new file mode 100644
index 000000000000..4018beaf71cc
--- /dev/null
+++
b/dsl/camel-cli-debug/src/main/java/org/apache/camel/component/cli/debug/CamelCliDebuggerFactory.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.component.cli.debug;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.debugger.DefaultBacklogDebugger;
+import org.apache.camel.spi.BacklogDebugger;
+import org.apache.camel.spi.CliConnector;
+import org.apache.camel.spi.Debugger;
+import org.apache.camel.spi.DebuggerFactory;
+import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.apache.camel.util.StopWatch;
+import org.apache.camel.util.concurrent.ThreadHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@JdkService(Debugger.FACTORY)
+public class CamelCliDebuggerFactory implements DebuggerFactory {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(CamelCliDebuggerFactory.class);
+
+ private final AtomicBoolean hangupIntercepted = new AtomicBoolean();
+
+ @Override
+ // Debugger is created and added as a service. This method always returns
a null object.
+ public Debugger createDebugger(CamelContext camelContext) throws Exception
{
+ // only create a debugger if none already exists
+ if (camelContext.hasService(BacklogDebugger.class) == null) {
+
+ // NOTE: the AutoCloseable object is added as a Service, hence it
is closed by Camel context
+ // according to the object lifecycle.
+ BacklogDebugger backlog =
DefaultBacklogDebugger.createDebugger(camelContext); // NOSONAR
+ backlog.setStandby(true);
+ backlog.setLoggingLevel("DEBUG");
+ backlog.setSingleStepIncludeStartEnd(true);
+
backlog.setInitialBreakpoints(BacklogDebugger.BREAKPOINT_ALL_ROUTES);
+ backlog.setSuspendMode(true); // wait for attach via CLI
+
+ // must enable source location and history
+ // so debugger tooling knows to map breakpoints to source code
+ camelContext.setSourceLocationEnabled(true);
+ camelContext.setMessageHistory(true);
+
+ // enable debugger on camel
+ camelContext.setDebugging(true);
+
+ // we need to enable debugger after context is started
+ camelContext.addLifecycleStrategy(new LifecycleStrategySupport() {
+ @Override
+ public void onContextStarted(CamelContext context) {
+ // noop
+ }
+
+ @Override
+ public void onContextStopping(CamelContext context) {
+ backlog.detach();
+ backlog.disableDebugger();
+ }
+ });
+ camelContext.addService(backlog, true, true);
+
+ // need to make debugger faster
+ CliConnector cli = camelContext.hasService(CliConnector.class);
+ if (cli != null) {
+ cli.updateDelay(100);
+ }
+
+ installHangupInterceptor();
+
+ long pid = ProcessHandle.current().pid();
+
+ LOG.info("=".repeat(80));
+ LOG.info("Waiting for CLI to remote attach (camel debug
--remote-attach --name={})", pid);
+ StopWatch watch = new StopWatch();
+ while (!hangupIntercepted.get() && !backlog.isEnabled() &&
!camelContext.isStopping()) {
+ try {
+ Thread.sleep(1000);
+ if (watch.taken() > 5000) {
+ LOG.info("Waiting for CLI to remote attach (camel
debug --remote-attach --name={})", pid);
+ watch.restart();
+ }
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+ if (backlog.isEnabled()) {
+ LOG.info("CLI debugger attached");
+ }
+ }
+
+ // return null as we fool camel-core into using this backlog debugger
as we added it as a service
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "camel-cli-debug";
+ }
+
+ private void handleHangup() {
+ hangupIntercepted.set(true);
+ }
+
+ private void installHangupInterceptor() {
+ Thread task = new Thread(this::handleHangup);
+ task.setName(ThreadHelper.resolveThreadName(null,
"CamelCliDebuggerHangupInterceptor"));
+ Runtime.getRuntime().addShutdownHook(task);
+ }
+
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
index c2bf8cbac795..8aac1e82db4c 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
@@ -42,8 +42,10 @@ import
org.apache.camel.dsl.jbang.core.commands.action.MessageTableHelper;
import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
import org.apache.camel.dsl.jbang.core.common.VersionHelper;
import org.apache.camel.main.KameletMain;
+import org.apache.camel.support.PatternHelper;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.StringHelper;
@@ -74,6 +76,10 @@ import static org.apache.camel.util.IOHelper.buffered;
@Command(name = "debug", description = "Debug local Camel integration",
sortOptions = false, showDefaultValues = true)
public class Debug extends Run {
+ @CommandLine.Option(names = { "--remote-attach" },
+ description = "Attaches debugger remotely to an
existing running Camel integration. (Add camel-cli-debug JAR to the existing
Camel application and run before attaching this debugger)")
+ boolean remoteAttach;
+
@CommandLine.Option(names = { "--breakpoint" },
description = "To set breakpoint at the given node id
(Multiple ids can be separated by comma). If no breakpoint is set, then the
first route is automatic selected.")
String breakpoint;
@@ -151,7 +157,12 @@ public class Debug extends Run {
printConfigurationValues("Debugging integration with the following
configuration:");
}
- Integer exit = runDebug();
+ Integer exit;
+ if (remoteAttach) {
+ exit = runRemoteAttach();
+ } else {
+ exit = runDebug();
+ }
if (exit != null && exit != 0) {
return exit;
}
@@ -170,10 +181,12 @@ public class Debug extends Run {
// read log input
final AtomicBoolean quit = new AtomicBoolean();
final Console c = System.console();
- Thread t = new Thread(() -> {
- doReadLog(quit);
- }, "ReadLog");
- t.start();
+ if (logLines > 0) {
+ Thread t = new Thread(() -> {
+ doReadLog(quit);
+ }, "ReadLog");
+ t.start();
+ }
// read CLI input from user
Thread t2 = new Thread(() -> doRead(c, quit), "ReadCommand");
@@ -205,6 +218,41 @@ public class Debug extends Run {
return 0;
}
+ private Integer runRemoteAttach() {
+ // find PID of running apps (if there are multiple then filter by name)
+ List<Long> pids = findPids(name);
+ if (pids.isEmpty()) {
+ return -1;
+ } else if (pids.size() > 1) {
+ printer().println("Name or pid " + name + " matches " + pids.size()
+ + " running Camel integrations. Specify a name
or PID that matches exactly one.");
+ return -1;
+ }
+ long pid = pids.get(0);
+
+ Path outputFile = getOutputFile(Long.toString(pid));
+ PathUtils.deleteFile(outputFile);
+
+ try {
+ JsonObject root = new JsonObject();
+ root.put("action", "cli-debug");
+ root.put("command", "attach");
+ if (breakpoint != null) {
+ root.put("breakpoint", breakpoint);
+ }
+ Path f = getActionFile(Long.toString(pid));
+ Files.writeString(f, root.toJson());
+ } catch (Exception e) {
+ return -1;
+ }
+
+ // attach to this pid
+ spawnPid = pid;
+ logLines = 0; // no logging possible
+
+ return 0;
+ }
+
private void doReadLog(AtomicBoolean quit) {
do {
if (spawnOutput != null) {
@@ -1209,4 +1257,65 @@ public class Debug extends Run {
}
+ List<Long> findPids(String name) {
+ List<Long> pids = new ArrayList<>();
+
+ // we need to know the pids of the running camel integrations
+ if (name.matches("\\d+")) {
+ return List.of(Long.parseLong(name));
+ } else {
+ if (name.endsWith("!")) {
+ // exclusive this name only
+ name = name.substring(0, name.length() - 1);
+ } else if (!name.endsWith("*")) {
+ // lets be open and match all that starts with this pattern
+ name = name + "*";
+ }
+ }
+
+ final long cur = ProcessHandle.current().pid();
+ final String pattern = name;
+ ProcessHandle.allProcesses()
+ .filter(ph -> ph.pid() != cur)
+ .forEach(ph -> {
+ JsonObject root = loadStatus(ph.pid());
+ // there must be a status file for the running Camel
integration
+ if (root != null) {
+ String pName = ProcessHelper.extractName(root, ph);
+ // ignore file extension, so it is easier to match by
name
+ pName = FileUtil.onlyName(pName);
+ if (pName != null && !pName.isEmpty() &&
PatternHelper.matchPattern(pName, pattern)) {
+ pids.add(ph.pid());
+ } else {
+ // try camel context name
+ JsonObject context = (JsonObject)
root.get("context");
+ if (context != null) {
+ pName = context.getString("name");
+ if ("CamelJBang".equals(pName)) {
+ pName = null;
+ }
+ if (pName != null && !pName.isEmpty() &&
PatternHelper.matchPattern(pName, pattern)) {
+ pids.add(ph.pid());
+ }
+ }
+ }
+ }
+ });
+
+ return pids;
+ }
+
+ JsonObject loadStatus(long pid) {
+ try {
+ Path f = getStatusFile(Long.toString(pid));
+ if (f != null && Files.exists(f)) {
+ String text = Files.readString(f);
+ return (JsonObject) Jsoner.deserialize(text);
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return null;
+ }
+
}
diff --git a/dsl/pom.xml b/dsl/pom.xml
index a3188d172db7..2089278bba19 100644
--- a/dsl/pom.xml
+++ b/dsl/pom.xml
@@ -42,6 +42,7 @@
<module>camel-endpointdsl</module>
<module>camel-componentdsl</module>
<module>camel-cli-connector</module>
+ <module>camel-cli-debug</module>
<module>camel-dsl-support</module>
<module>camel-dsl-modeline</module>
<module>camel-endpointdsl-support</module>
diff --git a/parent/pom.xml b/parent/pom.xml
index e672c43b33e9..896e389ce8ba 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -2912,6 +2912,11 @@
<artifactId>camel-cli-connector</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-cli-debug</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-componentdsl</artifactId>