How to Debug a Typescript Program Running on K8s Using Dap in Emacs?
Fleetinghow to debug a typescript program running on k8s, using ms-vscode.js-debug and dap-mode?
Build the code with support for sourcemaps. Run the container with a debug image
that runs node --inspect-brk
port forwarding
Create port forward on 9229 to be able to communicate with the remote node over the node debugging protocol.
Sometime, this error Error: Could not connect to debug target at http://localhost:9229: Could not find any debuggable target
indicates that the communication has been broken.
http http://127.0.0.1:9229/json/version 2>&1
http: error: ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) while doing a POST request to URL: http://127.0.0.1:9229/json/version
You should try to fix it before going further until you get the following:
curl http://127.0.0.1:9229/json/version
{
"Browser": "node.js/v18.15.0",
"Protocol-Version": "1.1"
}
Sometimes, you simply need to restart the pod. Refreshing the code while a debugger is running may cause this issue.
Also, in my experience, nestjs often fails to keep the debugger open and I see a bunch of “Starting inspector on 127.0.0.1:9229 failed: address already in use”.
use the appropriate code to use dap on emacs
Use the branch vscode-js-debug-feature of this fork of this fork of dap-mode1.
running with default dap-node (spoiler, it won’t work)
setup the configuration
Ensure the launch.json contains a suitable entry to connect to it.
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to app",
"address": "localhost",
"localRoot": "${workspaceFolder}/app",
"remoteRoot": "/app",
"port": 9229,
"sourceMaps": true,
"trace": true,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
}
}
Note that the type is “pwa-node” here. This is a legacy code and dap-mode must eventually use “node”, but so far, “node” is already reserved by the implementation of the legacy ms-vscode.node-debug2. When the fork will (hopefully) be merged upstream, we most likely will be able to use “node”.
get the source and source map support
Get the remote source AND associated source map to get the code AND the source mapping support.
You want at least the sources, the source map files and the node_modules (to step into the dependencies).
kubectl exec myapp -- tar -C /app -cf - . | tar -C . -xf -
You cannot simply pass kubectl cp myapp:/app app
because it does not deal
with symbolic links and at least the node_modules
is full of them.
killing the debugger
Note that you may have to kill the debugger for a fresh start from time to time
pkill -f ms-vscode
breakpoints not matched, what went wrong?
Also, sometimes the breakpoints are not matched remotely. You may have to run the debugger again.
You can try running this
(setq dap-print-io t)
(setq dap-inhibit-io nil)
(shell-command "pkill -f ms-vscode")
(with-current-buffer "*Messages*"
(read-only-mode -1)
(erase-buffer))
(when (dap--session-running (dap--cur-session))
(dap-disconnect (dap--cur-session-or-die)))
(konix/dap-delete-all-sessions)
Then running dap-debug again and see if there is a match for "verified": true,
. That means that one breakpoint was matched remotely. All "verified": null,
is a good hint that either your config is buggy, or that you need to run
it again.
Also, you can Add trace: true
into the launch.json entry. Then in
${TMPDIR-/tmp}
, you should find a file named
vscode-debugadapter-xxxx.json.gz
that gives a lot of data telling what was
send and received between the local dap bridge and the remote node.
You should see logs like
{
"tag": "cdp.receive",
"timestamp": 1704726925553,
"metadata": {
"connectionId": 0,
"message": {
"method": "Debugger.scriptParsed",
"params": {
"scriptId": "3825",
"url": "file://<remote-path>.js",
"startLine": 0,
"startColumn": 0,
"endLine": 140,
"endColumn": 42,
"executionContextId": 1,
"hash": "132c43512fb1cf792ed7af674bf2c2df2dcc962d",
"executionContextAuxData": {
"isDefault": true
},
"isLiveEdit": false,
"sourceMapURL": "<name>.map",
"hasSourceURL": false,
"isModule": false,
"length": 6466,
"scriptLanguage": "JavaScript",
"embedderName": "file://<remote-path>.js"
}
}
},
"level": 0
}
[...]
{
"tag": "runtime.sourcemap",
"timestamp": 1704726926476,
"message": "Mapped remoteToLocal: <remote-path>.js-> <local-path>.js",
"level": 0
}
[...]
{
"tag": "runtime.sourcecreate",
"timestamp": 1704726926891,
"message": "Creating source from url",
"metadata": {
"inputUrl": "file:///<remote-path>.js",
"absolutePath": "/<local-path>.js"
},
"level": 0
}
[...]
{
"tag": "dap.send",
"timestamp": 1704726927149,
"metadata": {
"connectionId": 1,
"message": {
"seq": 4239,
"type": "event",
"event": "loadedSource",
"body": {
"reason": "new",
"source": {
"name": "<remote-path>.js",
"path": "<remote-path>.js",
"sourceReference": 1411237085
}
}
}
},
"level": 0
}
[...]
{
"tag": "sourcemap.parsing",
"timestamp": 1704726927198,
"message": "Creating sources from source map",
"metadata": {
"sourceMapId": 48,
"metadata": {
"sourceMapUrl": "file:///<remote-path>.map",
"compiledPath": "/<local-path>.js"
}
},
"level": 0
}
beware the location of node_modules
Make sure you looked closely at the local path. It might be out of the
localRoot
folder. It has happened to me when working in a
yarn workspace. In that case,
using my local build, most of the dependencies node_modules where installed
in the root workspace while they were installed in the app in the
remote. Therefore my local build pointed towards ./root/node_modules/dep
while they were in ./root/app/node_modules/dep
remotely.
Actually, this would not have happened if I had followed my own advice and copied the whole /app folder.
Notes linking here
Permalink
-
Yeah… This is all still work in progress
↩︎