Remote Script Debugger
Introduction
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.
Server
Let’s create a simple Data Abstract for .NET server application using the New Project Wizard:
Debugger Service/h2>
To add the Remote Script Debugger service, you need to perform the following steps:
- Add a reference to the RemObjects.DataAbstract.Scripting.RemoteDebugger assembly.
- Open your service’s .RODL file and change its base RODL from DataAbstract.RODL to RemoteScriptDebugger.RODL.
- Add the ‘ScriptDebugger’ service and set its base service to the BaseScriptDebuggerService.
protectedoverride RemObjects.DataAbstract.IScriptDebugger GetScriptDebugger(){returnnew RemObjects.DataAbstract.Scripting.RemoteDebugger.RemoteScriptDebugger(this.SessionID);} |
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:
private IClientChannel fChannel;private IMessage fMessage;private EventReceiver fEventReceiver;private IBaseScriptDebuggerService fScriptDebugger;private Guid fSessionId; |
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);} |
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);} |
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));}));}
publicvoid FrameEnter(ScriptDebuggerEventSender sender, String functionName, String scriptSource, Boolean executionPaused){this.fSessionId= sender.SessionID; this.Invoke(new Action(delegate{this.ScriptSourceTextBox.Text= scriptSource;}));} |
publicvoid FrameExit(ScriptDebuggerEventSender sender, String functionName){} |
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);}));} |
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);}}} |
function beforeGetData(names, requestInfos){ log('Entering beforeGetData'); log(JSON.stringify(session)); log('Exiting beforeGetData');} |
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).
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.
Summary
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).