Debugging¶
Stop your code where it goes wrong and look at it : the values of its variables, the calls which led there, the value of any expression. Step through it line by line, into a utility class and back. All of it in the admin panel, in the Debug tab of the API testing page.
- Every language : TypeScript and JavaScript (Node.js, Bun), Python, PHP, C#, Go, Rust, Java and Kotlin.
- Every piece of code : custom APIs and their hooks, the hooks of instances, databases, tables and system APIs, listeners of events, schedulers, process initializers, migration scripts, WebSocket events, token generators, utility classes and test cases.
- Wherever it runs : in the sandboxes of your account, and on the native process of the server (
runOnNativeProcess). - Nothing to install : no IDE, no tunnel, no port to open. The admin panel is the debugger.
Turn it on¶
- Open Debug Settings in the menu of your user, turn Debugging on and Save. It is on for every server of the account ; nothing restarts.
- From then on, a request which asks for the debugger runs in a debug runner : the code stops at your breakpoints there. The other requests run as always.
- The requests of the API testing page ask for the debugger by themselves while debugging is on. Any other client asks with the header
x-am-run-in-debugger: true: the code stops while the API testing page is open in the admin panel. - A debug runner is the debug sandbox of the environment of the code (one per language and group of separate sandboxes), or, for code which runs on the native process, the debug runner of the server for your account. It starts with the first request which asks for it. A request which waits at a breakpoint is not timed out for an hour.
- Turn debugging off and save : every debug runner of the account ends, on every server.
The Debug tab¶
With debugging on, the API testing page opens on its Debug tab, the first tab of the right side, before Response. The page debugs for as long as it is open : the code stops at the breakpoints, and the tab shows where.
- The toolbar : what the code does now (running, or paused and where), and the buttons below.
- Variables : the scopes of the frame (locals, closures, globals), each variable opens to its members.
- Watch : expressions evaluated again at every stop, kept by your browser.
- Call Stack : the frames which led there. Click one to see its line and its variables. The frames of the runner, of the packages and of the runtime are hidden : Show brings them.
- Breakpoints : every breakpoint of the account by file : switch one off, go to its line, take them all out.
- Console, below them : what the code prints as it runs (
console.log,print,echo,fmt.Println,println!,Console.WriteLine,System.out.println), what the logpoints wrote, and any expression evaluated where the code stopped (the arrows bring back the earlier ones). Drag the line above it to give it more room. What the code prints is in the console of the account too.
When the code stops, the page shows the Debug tab, and the tab header says it with an amber dot. A response which comes while another tab is shown puts a green dot on Response.
Drag the line between the request and the response to give each side its width (double-click it to put it back in the middle) : the tabs and the editors of each side follow.
Breakpoints¶
- Click left of a line number in the code editor (or F9 on the line of the cursor) : a red dot is a breakpoint. Click it again to take it out.
- Right click the margin for more :
- Add Conditional Breakpoint… : the code stops only when the expression is true (
total > 100,user.role == "admin",len(items) > 3). - Hit Count : it stops from the nth time on (
3,>= 3), only the nth time (== 3), past the nth time (> 3), or every nth time (% 2). - Add Logpoint… : instead of stopping, it writes a message to the console of the debugger, each
{expression}in it evaluated where the code is (total is {total}). Its mark is a diamond. - Disable Breakpoint keeps it, grey, and the code does not stop there. Remove Breakpoint takes it out.
- Add Conditional Breakpoint… : the code stops only when the expression is true (
- A hollow dot is a breakpoint which is not bound yet : the code of the file has not run in the debug runner since it was set (it binds when it does).
- The breakpoints are the account's : every tab and every server of the account has them, and they follow the lines when you edit the code above them. Set them in the code editor of the request, or in the utility classes the header of the page opens.
Where the code stopped¶
Send the request : the code stops at the breakpoint, its line is shown in the editor, with the values of the variables at the ends of the lines above it.
| Action | Key | |
|---|---|---|
| Continue | F5 | to the next breakpoint |
| Step Over | F10 | to the next line |
| Step Into | F11 | into the function this line calls : a utility class too |
| Step Out | Shift+F11 | back to the code which called this function |
| Run to Line | Ctrl/Cmd+F10, or right click the margin | to the line of the cursor |
| Pause | stops the code where it runs | |
| Pause on exceptions | stops where an exception is thrown, caught or not ; a panic of Go and Rust |
- Hover a name in the editor while the code is stopped : its value.
- A file where the code stops which no editor of the page shows, a utility class the code stepped into for example, opens at the top of the Debug tab, with its breakpoints : the steps go on there, and back.
- When several runners stopped (two languages, two servers), the tab shows one : choose another in the list of its toolbar.
- Leave the page, or turn debugging off : the runners let the code go, it no longer stops at the breakpoints.
Java and Kotlin
The debugger evaluates the variables, their fields, the items of arrays, literals and operators (total > 100 && items.length == 3). It calls no method.
Code on the native process¶
Code with runOnNativeProcess: true runs in a runner of the server itself : a request which asks for the debugger runs in the debug runner of the server for your account, never in the runner every account shares.
The server makes the debug engine of the language ready the first time : debugpy for Python, Delve for Go, netcoredbg for C#, in a folder of its own. Node.js, Bun and the JVM have theirs. Rust needs lldb-dap on the server (Xcode on macOS, LLVM on Linux) and PHP the extension Xdebug : the answer of the request says how to install what is missing.
Security¶
Nothing but the server reaches the debugger of your code, and what it shows travels encrypted.
- Between the admin panel and the server, every request of the debugger and every event of it (where the code stopped, what the code printed) goes inside the encrypted channel of the admin panel : the server answers the people of the panel there alone, never in the clear. Only the people of the account reach its debugger. (The channel keeps what it carries from who only listens ; HTTPS also proves to the browser that your server answers.)
- In a sandbox, the debug engine listens on the loopback of the container : no port of the machine publishes it, and no other container or process reaches it over the network. The server comes through the agent of the sandbox, which takes only the connections that carry the token of that sandbox.
- On the native process, each engine is reached where only the server is let in : the inspector of Node.js names its target by an id the runner tells the server alone, the inspector of Bun answers at a secret path of its runner, debugpy takes no request before the secret of its runner, Xdebug connects to a socket of the server and gives the secret its runner holds, Delve listens on a socket only the user of the server reaches, netcoredbg and lldb-dap are started by the server for each connection, and the JVM connects to the server itself as it starts. The secrets are new for each debug runner.
- The debug runners of an account are the account's alone : the runners every account shares never debug.
- The sandboxes themselves are no wall against hostile code : what they protect, and what they do not.
Good to know¶
- Debugging is for your development servers : a runner stopped at a breakpoint holds its request.
- A request which asks for the debugger while debugging is off runs as every other request.
- The time a request waits at a breakpoint is not counted against its time : it waits for an hour.
- In Go and Rust, what the code printed just before it stopped can show in the console only once the code goes on : their runner reads it on a thread the debugger stops too.