Remote Script Debugger


Today I’d like to write about a new technology that is currently being developed by the RemObjects team – the Remote Script Debugger.

The Remote Script Debugger is a service that exposes several methods and events. It has to be explicitly implemented by a server application (in most cases in the same way the DataService is implemented, i.e. without any custom code). The Script Debugger service was designed in a way that allows to easily add (or remove) it to an already existing DataAbstract for .NET server. You’ll literally have to write just one line of code that instantiates the Remote Script Debugger on the server side.
As you’ve probably noticed, the Remote Script Debugger is not just some client debugger application – it is a base service that exposes debug information about Business Rules Scripts being executed by the server.
In this blogpost, I’ll show you how to add a Remote Script Debugger service to a server application and create a really simple debugger application.


Let’s create a simple Data Abstract for .NET server application using the New Project Wizard:

Use the PCTrade database as data source:

Don’t forget to add support for OData publishing – this will later allow to use a browser as data access application stub without writing any client application code.
Start the server and the open URL http://localhost:8099/odata/orders.
You should see some data in the server response (use the login/password ‘test’ when asked).

At this point, we have a working Data Abstract for .NET server. Now let’s add a debugger service to it.

Debugger Service/h2>
To add the Remote Script Debugger service, you need to perform the following steps:

  1. Add a reference to the RemObjects.DataAbstract.Scripting.RemoteDebugger assembly.
  2. Open your service’s .RODL file and change its base RODL from DataAbstract.RODL to RemoteScriptDebugger.RODL.
  3. Add the ‘ScriptDebugger’ service and set its base service to the BaseScriptDebuggerService.

  4. Close the Service Builder. Intf and _Impl files will be regenerated and a new ScriptDebuggerImpl file will be added.
  5. Open the ScriptDebugger_Impl file in the designer and point its ServiceSchemaName property to the same Schema the DataAbstract service uses.
  6. Open the data service implementation code and make sure that the ‘Debug’ property of the EcmaScriptDebugger component is set to true.
  7. Add a class override for the GetScriptDebugger method to the data service implementation:
    protectedoverride RemObjects.DataAbstract.IScriptDebugger GetScriptDebugger(){returnnew RemObjects.DataAbstract.Scripting.RemoteDebugger.RemoteScriptDebugger(this.SessionID);}

    This change allows the Data Service to instantiate the script debugger and wire up its events.
    8. Since the RemoteScriptDebugger uses events to send debugger data back to the client, you need to add EventSinkManager and MemoryMessageQueueManager components to your application. For Wizard-generated applications, the best place for network communication components is the Engine component. Don’t forget to set up the Message property of the newly added EventSinkManager component.

    And that’s all that is needed to expose the Remote Script Debugger service. In a real-world appication you should add some security checks to control who can access the remote script debugger service.

    Debugger Client

    Now let’s create a very simple debugger application. You can add code highlighting or a better variable inspector window later, but to keep the debugger client application simple, I’ll skip these steps for now.

    Create a new WinForms application. Add references to the Internet Pack for .NET, RemObjects for .NET and Data Abstract for .NET client assemblies, namely RemObjects.InternetPack, RemObjects.SDK, RemObjects.SDK.ZLib and RemObjects.DataAbstract.

    You’ll also need to generate an _Intf file based on the RemoteScriptDebugger.RODL. Just open the file in the Service Builder and generate the Interface code using the CodeGen menu.

    Now add 4 buttons, 2 Text Boxes and a ListView to the main form:

    Several private fields have to be introduced:

    private IClientChannel fChannel;private IMessage fMessage;private EventReceiver fEventReceiver;private IBaseScriptDebuggerService fScriptDebugger;private Guid fSessionId;
    The main form of our application has to implement the **IScriptDebuggerEvents** interface to be able to retrieve data about debug events from the server. Now let’s implement methods that send debugger commands (like **Step Into** or **Continue Execution**) to the server, and event handlers that are invoked when debugger events are sent by the server.
    privatevoid AttachButton_Click(object sender, EventArgs e){this.fChannel=new IpHttpClientChannel(){ TargetUrl =@"http://localhost:8099/bin"};this.fMessage=new BinMessage();this.fEventReceiver=new EventReceiver(){ Channel =this.fChannel, Message =this.fMessage};   // Initialize ScriptDebugger service proxythis.fScriptDebugger=new BaseScriptDebuggerService_Proxy(this.fMessage, this.fChannel, "ScriptDebugger");   // Attach Debuggerthis.fScriptDebugger.Attach(Guid.Empty, newString[]{"Enable Step By Step Debug"});   // Assign ScriptDebuggerEvents listenerthis.fEventReceiver.RegisterEventHandler(this, typeof(IScriptDebuggerEvents));}   privatevoid DetachButton_Click(object sender, EventArgs e){this.fScriptDebugger.Detach(Guid.Empty);}
    Note how we attach to the debugger by calling the **Attach** method. Its first parameter defines which session data access calls should be debugged. An empty Guid means that it’ll listen (i.e. debug Business Rules Scripts) to all sessions. It is possible to retrieve the session list from the server via the RemoteServiceDebugger service methods to be able to select specific session and debug scripts running in that context only. The code behind the Step and Continue/Run buttons is quite straightforward:
    privatevoid StepButton_Click(object sender, EventArgs e){this.fScriptDebugger.StepInto(this.fSessionId);}   privatevoid RunButton_Click(object sender, EventArgs e){this.fScriptDebugger.Continue(this.fSessionId);}
    Look at the **this.fSessionId** parameter. This method requires an exact identifier of the session being debugged, and does not allow **Guid.Empty** as a wildcard. This restriction is due to the fact that Step, Continue or Break Execution commands can only be sent to specific data access sessions. If the **Guid.Empty** wildcard was used, the Session identifier is sent by the server when any Business Rules Script method execution starts. Now we need to implement event handlers for the **FrameEnter**, **FrameExit**, **TracePoint**, **UnhandledException** and **Log** events. Each event is explained below, together with its handler implementation code. - **Log** event: The most obvious event of all. It is fired when the **log** script method is called.
    publicvoid Log(ScriptDebuggerEventSender sender, String message){this.Invoke(new Action(delegate{this.LogTextBox.AppendText(String.Format("{0} {1}: Log: {2}\r\n\r\n", DateTime.Now, sender.ScriptName, message));}));}
    • FrameEnter event: Fired when a script function is entered. It allows to retrieve the script source code, the name of function being executed and the session ID of the client that triggered the event.
      publicvoid FrameEnter(ScriptDebuggerEventSender sender, String functionName, String scriptSource, Boolean executionPaused){this.fSessionId= sender.SessionID;   this.Invoke(new Action(delegate{this.ScriptSourceTextBox.Text= scriptSource;}));}

      - FrameExit event: Fired when the debugger leaves the script method. In our sample application this event is not used, but in more advanced debbuger implementations, it would be a good place to remove current code line highlighting etc.
      publicvoid FrameExit(ScriptDebuggerEventSender sender, String functionName){}

      - TracePoint event: The most important event of the RemoteScriptDebugger. It is fired before some script code line is executed and provides information about the position of the code being executed and the script variable values.
      publicvoid TracePoint(ScriptDebuggerEventSender sender, ScriptDebuggerState debuggerState, Boolean executionPaused){this.Invoke(new Action(delegate{this.LogTextBox.AppendText(String.Format("{0} {1}: Executing line {2}\r\n\r\n", DateTime.Now, sender.ScriptName, debuggerState.StartLine));   this.VariablesList.Items.Clear();this.ShowVariablesInfo(debuggerState);}));}

      - UnhandledException event: Fired when script execution fails for some reason and Provides details about the exception that occurred.
      publicvoid UnhandledException(ScriptDebuggerEventSender sender, ScriptDebuggerState debuggerState, ScriptExceptionType type, String message, String stackTrace){this.Invoke(new Action(delegate{this.LogTextBox.AppendText(String.Format("{0} {1}: Failed at line {2}\r\n\r\n", DateTime.Now, sender.ScriptName, debuggerState.StartLine));this.LogTextBox.AppendText(String.Format("{0} {1}: {2} {3}\r\n\r\n", DateTime.Now, sender.ScriptName, type, message));   this.ShowVariablesInfo(debuggerState);}));}

      The ShowVariablesInfo method mentioned in the code above lists the script variable names and values. Variable values are serialized using the JSON serialization format. This allows to send variable values in a platform independent format.

      The sample debugger application being created in this post uses this ShowVariablesInfo method implementation:

      privatevoid ShowVariablesInfo(ScriptDebuggerState debuggerState){for(Int32 i =0; i < debuggerState.CallStack.Length; i++){var frame = debuggerState.CallStack[i];   ListViewItem item =new ListViewItem("[METHOD]"); item.SubItems.Add(String.Empty); item.SubItems.Add(frame.FunctionName);this.VariablesList.Items.Add(item);   foreach(var variable in frame.Variables){ item =new ListViewItem(variable.Name); item.SubItems.Add(variable.Type); item.SubItems.Add(variable.Value);   this.VariablesList.Items.Add(item);}}}

      Now we need to add a simple Busness Rules Script to our server application. I’ll add a simple script code like:

      function beforeGetData(names, requestInfos){ log('Entering beforeGetData'); log(JSON.stringify(session)); log('Exiting beforeGetData');}

      Testing the debugger

      Start the server again and open the URL http://localhost:8099/odata/orders.

      Now start the debugger App and press the ‘Attach’ button.

      Go back to the browser and reopen the http://localhost:8099/odata/orders.

      You’ll see the script source and status messages in our debugger application (press the Step button to run the script in step-by-step mode).

      That’s it. We have created a simple Business Script Rules remote debugger.

      I intentionally skipped advanced parts, like code highlighting and the proper display of variable values (i.e. deserializing for JSON form), as these are far beyond the scope of this post.


      This blogpost shows how you can easily add a Remote Script Debugger service to a Data Abstract for .NET based server application and how a simple debugger App can be created. Debugger application sources can be downloaded here.

      The Script Debugger service is already integrated into Relativity server. Alex Karpenko is working on the integration of the Script Debugger into Schema Modeler and it looks really cool (expect a blogpost about Script Debugger soon).