Article
Robert Cemper · Nov 26, 2021 13m read

Traditional Debugging in ObjectScript

Finding errors in your code or examining unexpected behavior is the main purpose of Debugging
I will try to refresh the traditional tools away from the helpers you have in Studio, VScode, Serenji, ....
to the basics which have been there before your preferred EDI used it in the background.

We see 3 typical situations:

  • Command-Line Debugging in the foreground from a terminal or similar session.
    • if this is possible the good old BREAK command (also with postcondition)
    • lets you stop and examine the situation as you may need it.
    • It is the oldest option dating back to before ObjectScript
    • newer but otherwise similar is the ZBREAK command that introduces
    • WatchPoints in addition to simple BreakPoints and a sophisticated set of conditions
    • here is an extended description of Command-line Debugging
    •  
  • Background Debugging
  • Everything described here is of course also useful and available for Command-line debugging
    • first, you have the option to write additional information into a (temporary) Global.
    • This is useful if you have already a rather clear suspicion of what to check
    • then, you can dump your actual variables also to SPOOL or use a redirected output  
    • or you call LOG^%ETN() to dump your whole partition into ^ERRORS Global  
    • and examine it with SMP/System Operation/Application Error Log
    • As a way to bring background to the command line, you may use Shell-based Debugging  
    • when you execute your code that never sees foreground in a Shell().  
    • This typically applies to SQL and CSP / ZEN
    •  
  • Shell-based Debugging
    • I start with a rather simple CSP page as an example that holds this code:
    • . . .
      </head>
      <script language="Cache" method="init" arguments="file,.tName" returntype="%Integer">
       BREAK  ;; <<<< for debugging
       if 'file {
        set tName="*** no file ***",index="" 
       } elseif $D(^ImportFile(file,-1)) {
       set tName=$O(^ImportFile(file,-1,"Class",""),1,index)
       } Else {
       set tName=$g(^ImportFile(file)),index=0
       }
       quit index
      </script>
      <script language="Cache" method=  
    • Now I run this from my CSPshell and see and get access to all variables and objects 
    • 
      IRISAPP>
      IRISAPP>do $system.CSP.Shell()
      CSP Shell
      
      CSP:IRISAPP>>> GET /csp/irisapp/Details.csp
      HTTP/1.1 200 OK
      Content-Type: text/html; charset=utf-8
      Set-Cookie: CSPSESSIONID-UP-csp-irisapp-=15340            15340; path=/csp/irisapp/;  httpOnly; sameSite=strict;
      Cache-Control: no-cache
      Date: Tue, 23 Nov 2021 19:03:29 GMT
      Expires: Thu, 29 Oct 1998 17:04:19 GMT
      Pragma: no-cache
      
      <html>
      <head>
      
      <!-- Put your page Title here -->
      <title> Details </title>
      
      <script language="javascript">
      window.onmessage = function(event){
      };
      </script>
       <!--
       function postDebug(item, index) {
              var escape = item.replace(/\//g, '-');  // fix slashes in dates
              //var escape = encodeURI(escape);
              var escape = encodeURIComponent(escape);  // fix % and more
              if (item == '') escape = 'NULL';
              //document.getElementById("div_debug").innerHTML += index + "," + escape + "<br>";
              var xhttp = new XMLHttpRequest();
              xhttp.onreadystatechange = function() {
                      if (this.readyState == 4 && this.status == 200) {
                              //document.getElementById("div_debug").innerHTML += this.responseText;
                      }
                      if (this.readyState == 4 && this.status != 200) {
                              //document.getElementById("div_debug").innerHTML += this.responseText;
                      }
              };
              //xhttp.open("GET", "/restAll/debug", true, "_SYSTEM", "SYS");
              //xhttp.open("POST", "/restAll/click/" + escape + '/' + index, true, "_SYSTEM", "abc123");
              xhttp.open("POST", "/restAll/click/" + escape + '/' + index, true, "_SYSTEM", "SYS");
              ;xhttp.send();
      }
      
      </script -->
      </head>
      
      <body>
      <table>
      
      <BREAK>zinit+1^csp.details.1     ;<<<<<<<<<<<<<<<<<<<<<<<<<<<
      IRISAPP 15e1>zwrite
      
      %CSPsc=1
      %SYSLOG=1
      %request=<OBJECT REFERENCE>[4@%CSP.Request]
      %response=<OBJECT REFERENCE>[1@%CSP.Response]
      %session=<OBJECT REFERENCE>[2@%CSP.Session]
      file=0
      tDEBUG=3
      tData="class,dc.data.rcc.GMbadge"
      tFile1=0
      tFile2=0
      tWhat="class"
      
      IRISAPP 15e1>zwrite %request
      %request=4@%CSP.Request  ; <OREF>
      +----------------- general information ---------------
      |      oref value: 4
      |      class name: %CSP.Request
      | reference count: 5
      +----------------- attribute values ------------------
      |            AppData = $lb("",64,1,"","/csp/irisapp/","",1,"","",0,1,"","","/csp/irisapp","IRISAPP","","c:\intersystems\iris\csp\irisapp\",1,"","",1,"",900,2,2,"",3600,0,1,1,"",1,"","",1,1,0,2,2)  <Set>
      |           AppMatch = "/csp/irisapp/"  <Set>
      |        Application = "/csp/irisapp/"  <Set>
      |  CSPGatewayRequest = 0  <Set>
      |            CharSet = ""  <Set>
      |              Class = "csp.details"  <Set>
      |        ContentType = ""  <Set>
      |   (ConvertCharSet) = "UTF8"
      | GatewayApplication = ""  <Set>
      |       GatewayBuild = ""  <Set>
      |GatewayConnectionName = ""  <Set>
      |       GatewayError = ""
      |   GatewayFunctions = ""  <Set>
      |GatewayInstanceName = ""  <Set>
      |       GatewayNewId = 0
      |GatewaySessionCookie = ""  <Set>
      |GatewaySessionIdSource = ""  <Set>
      |     GatewayTimeout = 60
      |             Method = "GET"
      |     NoResetTimeout = 0
      |           PageName = "Details.csp"
      |ProcessedRequestType = 1
      |           Protocol = "HTTP/1.1"  <Set>
      |    RegistryMethods = 1
      |          RequestId = ""
      |             Secure = 0  <Set>
      |            Service = "CSP"  <Set>
      |                URL = "/csp/irisapp/Details.csp"
      |          URLPrefix = ""
      |          UserAgent = ""  <Set>
      +----------------- swizzled references ---------------
      |          i%Content = ""
      |          r%Content = ""
      +-----------------------------------------------------
      
      IRISAPP 15e1>zwrite %session
      %session=2@%CSP.Session  ; <OREF>
      +----------------- general information ---------------
      |      oref value: 2
      |      class name: %CSP.Session
      |           %%OID: $lb("15340","%CSP.Session")
      | reference count: 5
      +----------------- attribute values ------------------
      |       %Concurrency = 1  <Set>
      |         AppTimeout = 900
      |        Application = "/csp/irisapp/"  <Set>
      |ApplicationLicenses = ""
      |        BrowserName = ""  <Get>
      |    BrowserPlatform = ""  <Get>
      |     BrowserVersion = ""  <Get>
      |         ByIdGroups = ""
      |   CSPSessionCookie = "15340            15340"
      |         CookiePath = "/csp/irisapp/"
      |         CreateTime = "2021-11-23 18:56:54"  <Set>
      |              Debug = 0  <Set>
      |         EndSession = 0  <Set>
      |          ErrorPage = ""
      |(EventClassContext) = ""
      |           GetNewId = 0
      |            GroupId = ""
      |  HttpAuthorization = ""
      |          KeepAlive = 1
      |                Key = "3"_$c(3)_"±Ð~"_$c(129)_"­t¼Ø´0"_$c(0)_"òÆÆùXNX"_$c(158)_"Å="_$c(152,1)_"T*"_$c(10)_"®«®"_$c(21)  <Set>
      |           Language = "de"
      |       LastModified = "2021-11-23 18:58:28"  <Set>
      |          LicenseId = "_SYSTEM"  <Set>
      |      LogoutCleanup = 0
      |      MessageNumber = 1  <Set>
      |          Namespace = "IRISAPP"
      |         NewSession = 0  <Set>
      |        (NoLicense) = 0
      |         OldTimeout = 5708603608  <Set>
      |  PersistentHeaders = ""  <Set>
      |           Preserve = 0  <Set>
      |          ProcessId = ""
      |           Referrer = ""  <Set>
      |       RunNamespace = ""  <Get,Set>
      |   SOAPRequestCount = 0
      |SecureSessionCookie = 0
      |    SecurityContext = $lb("_SYSTEM","%All,%DB_IRISLIB,%DB_IRISSYS","%All,%DB_IRISLIB,%DB_IRISSYS",32,-559038737)  <Set>
      |          SessionId = 15340
      |       SessionScope = 2
      |      (StickyLogin) = $lb("/csp/irisapp/","_SYSTEM",$lb("_SYSTEM","%All,%DB_IRISLIB,%DB_IRISSYS","%All,%DB_IRISLIB,%DB_IRISSYS",32,-559038737),"",0,0,0,1)  <Get,Set>
      |   UseSessionCookie = 2
      |          UserAgent = ""
      |    UserCookieScope = 2
      |             nosave = 0
      +--------------- calculated references ---------------
      |         EventClass   <Get,Set>
      |           Username   <Get>
      +-----------------------------------------------------
      
      IRISAPP 15e1>zwrite %response
      %response=1@%CSP.Response  ; <OREF>
      +----------------- general information ---------------
      |      oref value: 1
      |      class name: %CSP.Response
      | reference count: 5
      +----------------- attribute values ------------------
      |   AllowOutputFlush = 0
      |AvoidPartitionCleanup = 0
      |   (CSPGatewayData) = ""  <Set>
      |            CharSet = "utf-8"
      |      ContentLength = ""
      |        ContentType = "text/html"
      |         CookiePath = "/csp/irisapp/"
      |             Domain = ""
      |         GzipOutput = ""  <Set>
      |        HTTPVersion = ""
      |      HeaderCharSet = ""
      |Headers("CACHE-CONTROL") = "no-cache"
      |    Headers("DATE") = "Tue, 23 Nov 2021 19:03:29 GMT"
      | Headers("EXPIRES") = "Thu, 29 Oct 1998 17:04:19 GMT"
      |  Headers("PRAGMA") = "no-cache"
      |   IgnoreRESTOutput = ""
      |         InProgress = 1  <Set>
      |           Language = "de"  <Set>
      |   NoCharSetConvert = 0
      | OutputSessionToken = 1
      |           Redirect = ""
      | ServerSideRedirect = ""
      |             Status = "200 OK"
      |            Timeout = ""  <Set>
      |          TraceDump = 0
      |     UseASPredirect = 0  <Set>
      |        UseHttpOnly = 1
      |        VaryByParam = ""
      +--------------- calculated references ---------------
      |            Expires   <Get,Set>
      +-----------------------------------------------------
      
      IRISAPP 15e1>g
      
      <BREAK>zinit+1^csp.details.1
      IRISAPP 15e1>g
      
      <!-- tr><th colspan="3">#(tName)#</th></tr -->
      <tr><th>Line</th><th>*** no file ***</th><th>Line</th><th>*** no file ***</th></tr>
      
      </table>
      </body>
      </html>
    • The next example relates to SQL and I use this calculated property that fails every now and then
    • Property Random As %Numeric(SCALE = 2) [ Calculated, SqlComputed, 
         SqlComputeCode = { Set rcc=$Random(5) BREAK  Set {*} = $Random(10)/rcc} ];
      ----------------------------------------------------------------------------
      SQLquery= "select top 10 id,name,random from Sample.Person"  
      ----------------------------------------------------------------------------
      throws [SQLCODE: <-350> . . . ]   
      [%msg: <Unexpected error executing SqlCompute code for field 'Random': <DIVIDE>%0AmBk1+5^%sqlcq.DEMO.cls33.1>]
    • it is obvious that debugging %0AmBk1+5^%sqlcq.DEMO.cls33.1 is out of our scope
    •  
    • for debugging I use the SQLshell  to take advantage of my BREAK to see all variables and objects
    • DEMO> DEMO>do $system.SQL.Shell()
      SQL Command Line Shell
      ---------------------------------------------------- The command prefix is currently set to: <<nothing>>.
      Enter <command>, 'q' to quit, '?' for help.
      [SQL]DEMO>>select top 10 id,name,random from Sample.Person
      2.      select top 10 id,name,random from Sample.Person ID      Name    Random  . try {  Set rcc=$Random(5) BREAK  Set i%RandomO1 = $Random(10)/rcc
                                   ^
      <BREAK>%0AmBk1+5^%sqlcq.DEMO.cls33.1
      DEMO 10d3>zw %SNGetQueryStats=1
      SQLCODE=0
      rcc=1
      
      DEMO 10d3>zwrite $this
      45@%sqlcq.DEMO.cls33  ; <OREF>
      +----------------- general information ---------------
      |      oref value: 45
      |      class name: %sqlcq.DEMO.cls33
      | reference count: 6
      +----------------- attribute values ------------------
      |     %CurrentResult = "45@%sqlcq.DEMO.cls33"
      |      %CursorNumber = 1
      |  %ExtendedMetadata = $lb($lb("Sample.Person||ID","%Library.BigInt",18),$lb("Sample.Person||Name","%Library.String",10),$lb("Sample.Person||Random","%Library.Numeric",14))
      |           %Message = ""
      |       %Metadata(0) = $lb(3,"ID",-5,19,"0",1,"ID","Person","Sample",0,$c(0,0,0,1,0,0,0,0,0,0,0,0,0),"Name",12,50,"0",0,"Name","Person","Sample",0,$c(0,0,0,0,0,0,0,0,0,0,0,0,0),"Random",2,15,"2",1,"Random","Person","Sample",0,$c(0,0,0,0,0,0,0,0,0,0,0,0,0))
      |      (%NextColumn) = 1
      |           %Objects = ""
      | %OutputColumnCount = 0
      |        %Parameters = ""
      |          %ROWCOUNT = 0
      |             %ROWID = ""
      | %ResultColumnCount = 3
      |           %SQLCODE = 0
      |      (%SelectMode) = 0
      |     %StatementType = 1
      |        (%delock11) = ""
      |           %routine = ""
      |        CursorState = 1
      |              (ID1) = 1
      |             (IDO1) = ""
      |           (NameO1) = "Newton,Olga Z."
      |      (PpCallArgs1) = 10
      |        (Vrowcnt17) = 0
      |    (isolationMode) = 0
      |       (lockstat11) = 0
      |       (node2val21) = $lb("",82732383,"OptiPlex Media Inc.",34350,"V968","P1169","OR","E8259","Z4197","","Newton,Olga Z.","U4734","P4","U8988","W9047","992-61-9877",-92713954810893571,"2018-07-16 17:44:34")
      |           (rowcnt) = 0
      |         (rowlimit) = 9223372036854775807
      |        (starttime) = 9966.938434
      |             (time) = .000001
      |    (vpRUNTIMEOUT3) = "Newton,Olga Z."
      +----------------- swizzled references ---------------
      |   i%%PrivateTables = ""  <Set>
      |   r%%PrivateTables = ""  <Set>
      |    (i%%ProcCursor) = ""
      |    (r%%ProcCursor) = ""
      |          (i%%rsmd) = ""
      |          (r%%rsmd) = "50@%SQL.StatementMetadata"
      +--------------- calculated references ---------------
      | %StatementTypeName   <Get>
      |                 ID   <Get>  [ Aliases - id ]
      |               Name   <Get>  [ Aliases - name,NAME ]
      |             Random   <Get>  [ Aliases - random,RANDOM ]
      +----------------------------------------------------- DEMO 10d3>g
              Newton,Olga Z.  1  . try {  Set rcc=$Random(5) BREAK  Set i%RandomO1 = $Random(10)/rcc
                                   ^
      <BREAK>%0AmBk1+5^%sqlcq.DEMO.cls33.1
      
      DEMO 10d3>zwrite %SNGetQueryStats=1
      SQLCODE=0
      rcc=1
      DEMO 10d3>g
              Adam,Emily G.   7  . try {  Set rcc=$Random(5) BREAK  Set i%RandomO1 = $Random(10)/rcc
                                   ^
      <BREAK>%0AmBk1+5^%sqlcq.DEMO.cls33.1
      
      DEMO 10d3>zw %SNGetQueryStats=1
      SQLCODE=0
      rcc=0
      
      DEMO 10d3>g   ; now we get the error
      [SQLCODE: <-350>]
      [%msg: <Unexpected error executing SqlCompute code for field 'Random': <DIVIDE>%0AmBk1+5^%sqlcq.DEMO.cls33.1>]
      2 Rows(s) Affected
      statement prepare time(s)/globals/cmds/disk: 0.0003s/4/137/0ms
                execute time(s)/globals/cmds/disk: 44.5198s/37/839/0ms
                                cached query class: %sqlcq.DEMO.cls33
      ---------------------------------------------------------------------------
      [SQL]DEMO>>q 
      DEMO>
    • And as you see these debugging methods work also today in IRIS .
    • This is my Personal Best Practice.
19
4 868
Discussion (10)5
Log in or sign up to continue

Didn't know about the SQL shell. Thank you!

Didn't know about the CSP shell. Thank you!

Very useful, thank you! Debugging in ObjectScript always eldued me so far!

How would one go and combine this with the VSCode ObjectScript debugger?

I have been searching this for long!

Why is this not covered anywhere in documentation or learning?

I want more !

like it, but my "like" is not accepted and stays grey
 

I thought I knew most of the of the debugging tricks in Caché, Ensemble and IRIS, and I have used the sql shell but had not used it the csp shell and I though I have found debugging basic csp pages using a log global and JS popup messages ( simplistic but they worked). However, when it came to Zen bebugging was a nightmare. I could view the page source and inspect the Dom but the sleepless nights I had with hgroups and vgroups and the inheritance of CSS styles could not explain why, on a form containing 15 text boxes, drop-down mists and some check boxes with all my captions neatly aligned left and my data entry controls aligned left in the second inner vgroup (page has outer vgroup, then an hgroup for my heading, and then two vgroups for my captions and data controls) and on one row out of 15, the caption was indented by 5 Characters of the data control would be rightalligned. For absolutely no obvious reason at all. I used the inspector to nest through every page element, the css in play, and the various in-line, align="left" (or equivalent) and I could spend days trying to work out why this one element was misbehaving. I spent more time debugging stupid things like this than I had spent in total in writing all of the Class Zen Get and Set methods, the sql queries to populate lists, getting the correct syntax for selected item vs identifier item and the menus and all the graphics and style sheets put  together. I never did find an easy way to debug these layout issues and I wonder if you had any tricks for resolving these silly layout issues that literally cost me days in testing?

Nigel

Hi Nigel,
The CSP shell is rather limited. As it is not a browser, not running any CSS or JS it is only useful
for basic cases with static layouts but is unable to do any dynamic stuff as required by ZEN or jquery.
It is really just for CSP in the original sense.
 

I'm helping maintain a fairly old code base that wasn't built around test-driven development.  I know debugging and testing are two different things, but I often find that the only way to test my code is to use the debugger.  If I had better ways of stepping and out of entry points to automate these tests it would be great, but the debugger doesn't work like that. 

Does anyone else find that their unit testing is also using the debugger?