Implement snapshot debugging

Completed

A common support scenario for consultants is to be contacted by the customer with an issue in their solution that needs troubleshooting to determine the cause and the location of the issue in the code.

While there's support for creating sandboxes with a copy of production data, and debug/stop program flow on that without impacting a customer's production tenant, in some cases the customer is blocked and the consultant is under heavy time pressure to investigate and resolve the issue without the time required to provision a duplicate environment and reproduce the issue there.

To address this, Microsoft has introduced the ability to attach the Visual Studio Code AL debugger to a production tenant to take snapshots of code execution, allowing rapid investigation and collaboration with the customer on exact reproduction steps.

With the new snapshot feature, you can:

  • Set snappoints in code.

  • Create a new snapshot attach configuration. This could be web client, web API, or background session (by specifying user ID or session ID, no selection UI yet).

  • Attach to an environment in snapshot mode.

  • Perform reproduction steps to trigger snappoints.

  • Download snappoint in Visual Studio Code after completing reproduction.

  • Inspect stack trace/program execution and variables at snappoints offline in Visual Studio Code.

Snapshot debugging allows a delegated admin to record AL code that runs on the server, and once it has run, debug the recorded snapshot in Visual Studio Code. For a delegated admin to create and download a snapshot file that exists on the server on behalf of an end-user, the delegated admin must be part of the D365 Snapshot Debug permission group.

One of the advantages of snapshot debugging is that it provides the ability to inspect code execution and variables in the production environment in a cloud service, on a specified user session.

Snapshot debugging introduces the concept of snappoints. A snappoint is a breakpoint in Visual Studio Code that is set when creating a snapshot, they don't, however, stop execution of code like when using regular debugging. Snappoints instruct execution to log the state at the breakpoint for later offline inspection.

Snapshot debugging will record AL code as it runs on the server, but will only collect variable information on:

  • Snappoints

  • AL exceptions

Initialize a snapshot debugging session

From Visual Studio Code, you start a snapshot by creating a snapshot configuration file.

An Initialize Snapshot screenshot example.

There are two template configurations for a snapshot, which are accessed by selecting Add Configuration in Visual Studio Code.

  • AL: Initialize a snapshot debugging session locally

  • AL: Initialize a snapshot debugging session on cloud

Choose whether to run the session on a cloud service or locally.

The configuration file should contain the following information:

  • userId - The GUID of the user on whose behalf a snapshot debugging will be started. For on-premises, this can also be the user name in user password authentication scenarios. The user must be able to start, or have a session type opened that is specified in the breakOnNext parameter.

  • sessionId - A session ID for the user specified above in userId.

  • snapshotVerbosity - Determines how much execution context to be recorded. If SnapPoint is specified, then only methods that hit a snappoint is recorded.

Snapshot settings in the launch.json file.

When a configuration is defined, a snapshot debugging session can be initialized by pressing Ctrl+Shift+P and then selecting AL:Initialize Snapshot Debugging or by pressing F7.

Search for Initialise Snapshot Debugging example.

To record the AL execution, the server will now wait for a connection to happen where the following rules apply:

  • If a sessionId is specified for a userId for a given tenant, then it's that session that is snapshot debugged.

  • If only a userId is specified for a given tenant, then the next session that is specified in the breakOnNext configuration parameter is snapshot debugged.

  • If no userId is specified, then the next session on a given tenant that validates the breakOnNext parameter will be snapshot debugged.

Once a snapshot debugging session is initialized the snapshot debugging session counter on the status-bar will be updated and look like this:

Visual of the snapshot debugger session counter.

Status of a snapshot debugging session

Clicking on the status bar icon or pressing Shift+F7 will bring up a list of all available snapshots.

Information about the available snapshots.

The status list shows the state of a snapshot-debugged session. A snapshot debugging session can be in one of these statuses:

  • Initialized - A request is issued and the server is waiting for the next session to be snapshot debugged based on the above rules.

  • Started - You've attached to an end-user session to snapshot debug.

  • Finished - When the snapshot debugging session has finished.

  • Downloaded - When the snapshot file is downloaded.

Finish a snapshot debugging session

You finish a snapshot debugging session by pressing Alt+F7. This brings up all snapshot sessions that have been started. Choosing one closes the session debugging on the server and download the snapshot file.

The snapshot file can contain customer privacy data and must therefore be handled according to privacy compliance and should be deleted when it isn't needed anymore.

Snapshot debugging sessions that have produced a snapshot file can be debugged. The location of a snapshot file is controlled by the al.snapshotOutputPath configuration parameter. By default it's local to the current workspace and it's called ./.snapshots.

Download symbols on the snapshot debugger endpoint

In order to download symbols on a production server, you need permission related entries:

  • Be a delegated admin

  • The read-only access to the Published Application table emphasized in the D365 EXTENSION MGT permission set should also be granted.

Debugging requires that symbols on the server are matched with the symbols that the user has locally. If not, and you set a breakpoint on a given line in Visual Studio Code, the line of code may differ from what is on the server.

Symbols download is using the snapshotInitialize debug configuration settings in Visual Studio Code, which is set up when you choose either AL: Initialize a snapshot debugging session locally or AL: Initialize a snapshot debugging session on cloud.


{
            "name": "snapshotInitialize: MyServer",
            "type": "al",
            "request": "snapshotInitialize",
            "environmentType": "OnPrem",
            "server": "http://localhost",
            "serverInstance": "BC170",
            "authentication": "UserPassword",
            "breakOnNext": "WebClient"
},

Debugging requires that symbols on the server are matched with the symbols that the user has locally. If this isn't the case, and you set a breakpoint on a given line in Visual Studio Code, the line of code may differ from what is on the server. This is why you must download symbols from production servers for snapshot debugging in order for a breakpoint set on one line to match with what the server understands of this line. This is to avoid a scenario where you set a breakpoint in a DAL file on line 12, but line 12 on the server is an empty line or a different line if the symbols aren't the same.

Debugging a snapshot file

There are two user actions that start snapshot debugging.

  • Creating a new launch debug configuration and specifying the snapshot file name in the snapshotFileName configuration setting. This is the only setting that is needed besides the type, request, and name.

  • Clicking on the status icon or by pressing Shift+F7 and selecting a finished snapshot-debugged session.

Once a snapshot debugging session starts in Visual Studio Code, code execution stops at the first snappoint. AL exceptions are treated as snappoints, with the only difference that they can't be removed by user actions. Other snappoints are regular breakpoints that can be removed or readded by user actions. If no snappoints are specified the first recorded methods, the first line is the entry breakpoint.

The user can set breakpoints and continue execution to that breakpoint for testing, for example, if a line is hit, but it's the snappoint that carries the real information.

The AL Profiler

With the AL Profiler for the AL Language extension you can capture a performance profile of the code that was executed for a snapshot. Using the performance profiling editor view in Visual Studio Code, you can investigate the time spent on execution, using top-down and bottom-up call stack views. The AL profiler works on a snapshot of running code.

To do profiling on code, you must first capture a snapshot of running code. The snapshot configuration has a parameter called executionContext which has the following possible values. If nothing is specified, the configuration is DebugAndProfile by default.

  • Debug - The snapshot session won't gather profile information.

  • Profile - The snapshot session will only gather profile information, snappoints will be ignored, and debugging won't work.

  • DebugAndProfile - Both debugging and profiling will be available as a result of a snapshot session. Again, this is the default setting.

This means that if we want to use the snapshot both for debugging and profiling purposes, the configuration for the snapshot in the launch.json file, must look equivalent to the following:

"configurations": [ 
        {
            "name": "snapshotInitialize: Your own server",
            "type": "al",
            "userId": "555",
            "request": "snapshotInitialize",
            "environmentType": "OnPrem",
            "server": "http://localserver",
            "serverInstance": "BC190",
            "authentication": "Windows",
            "breakOnNext": "WebClient",
            "executionContext": "DebugAndProfile"
        },
    ...

Then, when the snapshot file is downloaded, you can generate a profile file. This can be done in one of two ways:

  • Open the Command Palette by using the Ctrl+Shift+P shortcut, then select the AL: Generate profile file command and choose a snapshot from the dropdown menu.

  • In Visual Studio Code explorer, right-click the specific snapshot file and choose Generate Profile File.

To investigate the graph of method calls, you open the generated profile file in the performance profiling editor. If you select the file directly, it opens in top-down view. You can also right-click a profile file and get the following options:

  • AL Profile Visualizer TopDown Graph

  • AL Profile Visualizer BottomUp Graph

When the profile file opens, it looks like the illustration below:

Screenshot showing what a sample profile file typically looks like.

To investigate the data shown in the graph, you can use different view modes. To switch between views, you can either right-click the profile file and select a view, or you can use the small button in the upper right corner. There are two different view modes in the graph; top-down and bottom-up.

When sorting the stack top-down, the graph sorts the methods according to call sequence, which means that the child nodes are the methods called from the parent node. And when sorting bottom-up, the graph is sorted as a reverse call stack, which means that the child nodes are methods who called the parent node.

To investigate further, the Self-time and Total time columns are important indicators of where time is spent in the code. The Self-time is the amount of time spent in the method only, excluding any calls out of the method. The Total time is the amount of Self-time plus any calls out of the method. On bottom-up graphs, the Total time and Self-time columns are sortable. Clicking the columns will first sort them ascending, and clicking again will sort them descending.

Hit count is only available on top-down graphs and shows the number of times a specific method was called. Time spent is aggregated.

The nodes in the graph can be filtered. The syntax is the following:

@column name | <alias> <op> <value> where
<column name> := [function, url, path, selfTime, totalTime, id, objectType, objectName, declaringApplication]

The aliases that are available for the column names are:

<alias> := [f, u, p, s, t, id, ot, on, da]
<op> := [numeric operators, boolean operators, string operators]
numeric operators : [:, =, >, <, <=, >=, <>, !=]
: := equal
boolean operators : [:, =, <>, !=]
string operators : [:, =, !=, <>, ~, =]
~ = := <regex> 

Visualize code lines executed in snapshot capture

Snapshot debugging is a powerful way of troubleshooting Business Central cloud production environments. Because snapshot captures are non-interactive, snap points must be set beforehand, typically making snapshot debugging an iterative process. To increase efficiency in determining which code was executed, for example, conditional code paths, and help locate good candidates for setting new snap points to investigate variable state for code execution, visual cues have now been added to the snapshot playback. These cues are displayed as vertical lines in the left gutter of the code editor.

During snapshot playback in Visual Studio Code, the left-side code editor gutter contains a vertical visual bar to indicate which code was executed in the snapshot capture.

The color of the gutter bar can be controlled using the new al.snapshotDebuggerLinesHitDecoration in the settings.json file.