luceedebug is a step debugger for Lucee.
There are two components:
The java agent needs a particular invocation and needs to be run as part of the JVM/Lucee server startup.
The VS Code client extension is available as luceedebug
when searching in the VS Code extensions pane (or it can be built locally, see subsequent instructions).
Min supported JDK is JDK11. Building requires JDK17 or higher.
Built jars are available via github 'releases'. The most recent build is https://github.com/softwareCobbler/luceedebug/releases/latest
The following steps will build to: ./luceedebug/build/libs/luceedebug.jar
cd luceedebug
./gradlew shadowjar
cd luceedebug
gradlew.bat shadowjar
Note that you must be running a JDK version of your java release (a common error on startup when running "just" a JRE is java.lang.NoClassDefFoundError: com/sun/jdi/Bootstrap
).
Add the following to your java invocation. (Tomcat users can use the setenv.sh
file for this purpose.)
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=localhost:9999
-javaagent:/abspath/to/luceedebug.jar=jdwpHost=localhost,jdwpPort=9999,debugHost=0.0.0.0,debugPort=10000,jarPath=/abspath/to/luceedebug.jar
agentlib
: Configures JDWP, which is the lower-level Java debugging protocol that the luceedebug agent connects with. (Note: The VS Code debugger connects to the luceedebug agent, not JDWP, so JDWP/agentlib
usually doesn't need to be modified/customized.)
address
: Leave this as localhost:9999
, unless you have a compelling reason to change it (e.g., if some other service is already listening on port 9999).javaagent
: Configures the luceedebug agent, itself.
/abspath/to/luceedebug.jar
(the first token in the javaagent
): The absolute path by which your server can find the luceedebug agent library. You must change this to match your environment.jdwpHost
/jdwpPort
: The luceedebug agent connects to JDWP via this host/port. These values must match those in agentlib
's address
.debugHost
/debugPort
: These configure the host/port that the VS Code debugger attaches to.Set this to the interface on which you want the debugger to listen. In non-docker environments, this would be the IP address of a particular interface.
If Lucee is running in a docker container, the debugHost
must be 0.0.0.0
(i.e., "listen on all interfaces"). However, be careful not to use this value on a publicly-accessible, unprotected server, as you could expose the debugger to the public (which would be a major security vulnerability).
jarPath
: This value must be identical to the first token in the javaagent
arguments. Unfortunately, we have to specify the path twice! One tells the JVM which jar to use as a java agent, the second is an argument specifying from where the java agent will load debugging instrumentation.(There didn't seem to be an immediately obvious way to pull the name of "the current" jar file from an agent's premain
, but maybe it's just been overlooked. If you know let us know!)
The VS Code luceedebug extension is available on the VS Code Marketplace. If you are an end-user who just wants to start debugging your CFML, install the luceedebug extension from the Marketplace.
name
key of the debug configuration. (In the configuration example, below, it would be Project A
.)If you want to hack the extension, itself, build/run instructions follow.
Prerequisites:
npm
typescript
brew install typescript
# vs code client
cd vscode-client
npm install
npm run build-dev-windows # windows
npm run build-dev-linux # mac/linux
Steps to run the extension in VS Code's "extension development host":
cd vscode-client
code . # open vs code in this dir
A CFML debug configuration looks like:
{
"type": "cfml",
"request": "attach",
"name": "Project A",
"hostName": "localhost",
"port": 10000,
// optional; only necessary when ide and lucee paths don't match
"pathTransforms": [
{
"idePrefix": "${workspaceFolder}",
"serverPrefix": "/app"
}
]
}
hostName
/port
should match the debugHost
/debugPort
of the Java agent's configuration. (There are exceptions; e.g., on remote hosts where DNS and/or port forwarding are in play.)
pathTransforms
pathTransforms
maps between "IDE paths" and "Lucee server paths". For example, in your editor, you may be working on a file called /foo/bar/baz/TheThing.cfc
, but it runs in a container and Lucee sees it as /serverAppRoot/bar/baz/TheThing.cfc
.
In the case of local debugging (when there are no virtual machines or containers involved), you may not need a pathTransforms
configuration, because both your IDE and Lucee probably know any given CFML file by the same path name.
However, in environments where the IDE path of a CFML file isn't identical to the Lucee path, luceedebug needs to know how to transform these paths.
Currently, it is a simple prefix replacement, e.g.:
"pathTransforms": [
{
"idePrefix": "/foo",
"serverPrefix": "/serverAppRoot"
}
]
In the above example, the IDE would announce, "set a breakpoint in /foo/bar/baz/TheThing.cfc
, which the server will understand as "set a breakpoint in /serverAppRoot/bar/baz/TheThing.cfc
".
Omitting pathTransforms
means no path transformation will take place. (It can be omitted when IDE paths match server paths.)
Multiple pathTransforms
may be specified if more than one mapping is needed. The first match wins.
Example:
"pathTransforms": [
{
"idePrefix": "/Users/sc/projects/subapp_b_helper",
"serverPrefix": "/var/www/subapp/b/helper"
},
{
"idePrefix": "/Users/sc/projects/subapp_b",
"serverPrefix": "/var/www/subapp/b"
},
{
"idePrefix": "/Users/sc/projects/app",
"serverPrefix": "/var/www"
}
]
In this example:
/Users/sc/projects/app/Application.cfc
will match the last transform and map to /var/www/Application.cfc
on the server./Users/sc/projects/subapp_b_helper/HelpUtil.cfc
will match the first transform and map to /var/www/subapp/b/helper/HelpUtil.cfc
on the server.writeDump(x)
and serializeJSON(x)
data visualizations are made available as context menu items from within the debug variables pane. Right-clicking on a variable brings up the menu:
and results are placed into an editor tab.
Support for conditional breakpoints, watch expressions, and REPL evaluation.
request.xxx
, where request.xxx
is usually null but is sometimes set to true, is a sensible thing.x = 42
(an assignment, as opposed to the equality check x == 42
) will assign x
the value of 42
.If breakpoints aren't binding, you can inspect what's going using the "luceedebug: show class and breakpoint info" command. Surface this by typing "show class and breakpoint info" into the command palette.
./gradlew dependencyCheckAnalyze