Skip to content

Debugging & writing COS scripts

Micky Hulse edited this page Aug 2, 2013 · 18 revisions

It should go without saying, but it's always best to develop and test new code on your test server before bringing over to a production machine.

Google Groups

I always recommend searching/using the below Google groups if you have questions about Caché/COS/CSP/Lightning:

  1. Caché, Ensemble, DeepSee
  2. DTI: Lightning-dev (English)

My typical workflow

With that said, when I start a new script, I usually create a "test" CSP file and put it in a /demos folder at the root of my publication. I like to start fresh scripts in this folder, outside of a defined Lightning "Section", because I can really focus on the code and see the errors that normally are hidden due to the way the DTI is setup to suppress errors.

Once I create a new csp page, I start simple. I focus on all the small bits and pieces that will eventually make up the larger part of my code. Even if the code isn't for a larger script/project, I still like to use these standalone csp pages to test even the smallest bit of code.

If I need a gStory or gSection object, I'll use the <csp:object ...> tag. For example:

<csp:object name="gStory" classname="dt.cms.schema.CMSStory" objid="30205958">
#(gStory)#
<csp:object name="gSection" classname="dt.cms.schema.Section" objid="50">
#(gSection)#

Once I confident that my code is ready for the real world, I'll move it into real templates.

Argumentless zwrite:

When I'm developing COS, I always put this code at the bottom of my test page:

<script language="cache" runat="server">
	
	; Debug:
	write !, "<pre>"
	try {
		set currIO = ##class(%SYS.NLS.Device).SetIO("HTML")
		zwrite
	}
	catch {
		write "ERROR: ", $ZERROR
	}
	if ($get(currIO) '= "") {
		do ##class(%SYS.NLS.Device).SetIO(currIO)
	}
	write "</pre>"
	
</script>

Without any other code on the page, this should be similar to the output you'll see:

%CSPsc=1
%request=<OBJECT REFERENCE>[1@%CSP.Request]
%response=<OBJECT REFERENCE>[2@%CSP.Response]
%session=<OBJECT REFERENCE>[3@%CSP.Session]
currIO="UTF8"

The above code is extremely useful for when it comes to catching leftover variables (e.g., variables you forgot to kill) and/or when there's an error in the code.

External links:

$system.OBJ.*

I love me some %SYSTEM.OBJ methods.

Here's the %SYSTEM.OBJ Class docs:

One extremely useful bit of code is the Dump() method.

Example:

<pre>
#[ do $system.OBJ.Dump(gStory) ]#
</pre>

The above will dump everything you need to know about the gStory object to the CSP page. It's cool! Give it a try. :)

$system.OBJ.* + argumentless zwrite

Below are a few examples of using how I use $system.OBJ and argumentless zwrites on our system.

Using a query string to display helpful debug and object information:

Put this code into a CSP file:

<csp:if condition='$data(%request.Data("FOO", 1))'>
	
	<hr>
	
	<div class="wiffle">
		
		<section>
			
			<h1 class="head">FOO!</h1>
			
			<csp:if condition=($isobject($get(gStory)))>
				
				<h2><code>gStory</code></h2>
				
				<pre>#[ do $system.OBJ.Dump(gStory) ]#</pre>
				
				<h2><code>gStory.getStory()</code></h2>
				
				<pre>#[ do $system.OBJ.Dump(gStory.getStory()) ]#</pre>
				
			</csp:if>
			
			<csp:if condition=($isobject($get(gSection)))>
				
				<h2><code>gSection</code></h2>
				
				<pre>#[ do $system.OBJ.Dump(gSection) ]#</pre>
				
			</csp:if>
			
			<h2><code>%request</code></h2>
			
			<pre>#[ do $system.OBJ.Dump(%request) ]#</pre>
			
			<h2><code>zwrite</code></h2>
			
			<script language="cache" runat="server">
				
				write !, "<pre>"
				try {
					set currIO = ##class(%SYS.NLS.Device).SetIO("HTML")
					zwrite
				}
				catch {
					write "ERROR: ", $ZERROR
				}
				if ($get(currIO) '= "") {
					do ##class(%SYS.NLS.Device).SetIO(currIO)
				}
				write "</pre>"
				
			</script>
			
		</section>
		
	</div> <!-- /.wiffle -->
	
</csp:if>

On your primary Section template, call it like so:

<dti:file:include base="assets" file="/includes/FOO.csp" />

Test the code by adding ?FOO to the url.

A "real world" example:

On a test page, using my argumentless zwrite, I was seeing this output:

%CSPsc=1
%ROWCOUNT=1
%ROWID=16108
%objcn=1
%objlasterror="0 ;�-%LoadData+13^dt.cms.seo.DefaultNaming.1:CMS"
%request=<OBJECT REFERENCE>[1@%CSP.Request]
%response=<OBJECT REFERENCE>[2@%CSP.Response]
%session=<OBJECT REFERENCE>[3@%CSP.Session]
currIO="UTF8"
gStory=<OBJECT REFERENCE>[4@dt.cms.schema.CMSStory]

See the problem? Check out the value of %objlasterror.

Yah, not very helpful.

Let's see if this helps:

<pre>#[ do $system.OBJ.Dump(%objlasterror) ]#</pre>

Output:

'0 ;   ±         - %LoadData+13^dt.cms.seo.DefaultNaming.1:CMS' is not an oref value.

Not much more helpful, but at least it gave me a little more info.

Let's parse %objlasterror even further (obviously, you'll want to make sure %objlasterror is an object and exists before dumping it):

<pre>#[ do $System.OBJ.DisplayError(%objlasterror) ]#</pre>

Output:

ERROR #5809: Object to Load not found

Ok, now we're getting somewhere.

Checking the docs:

2009.1: Caché Error Reference: Object Error Messages

Error code #5809 says:

Object to Load not found

Nothing I didn't already know.

Searching the Caché group for "5809" doesn't yield any helpful results either. Dang-it!

I've kinda hit a dead end here.

Going back to the original error message:

%objlasterror="0 ;�-%LoadData+13^dt.cms.seo.DefaultNaming.1:CMS"

Not knowing which bit of code was causing this error, I started commenting out blocks of code and testing one line at a time.

I soon discovered that the problem was originating from this code:

story.getLink(section, layout)

From there, in order to rule out all other code, I pulled the link code from getLink() method and tested it on a page all by itself:

set link = ##class(dt.cms.support.StoryLink).%New()
do link.init(gStory, gStory.homeSectionID, gStory.defaultFullLayout)
w link

Interesting! I still get the same %objlasterror error!

In Studio, looking at the init() Method in dt.cms.support.StoryLink.cls, I see references to ##class(dt.cms.seo.DefaultNaming).%New().

... looking at dt.cms.seo.DefaultNaming.cls I see (in the comments) a lot of talk about SEO path building and default setups.

From there, I decide to use DB Visualizer to look at the DefaultNaming table:

select * from dt_cms_seo.DefaultNaming;

When executing the above query I get:

19:02:51  [SELECT - 0 row(s), 0.002 secs]  Empty result set fetched
... 1 statement(s) executed, 0 row(s) affected, exec/fetch time: 0.002/0.000 sec  [0 successful, 1 warnings, 0 errors]

This table has no data.

Then it clicks! We need to setup our SEO Default Naming!

The moral of the story is: I would have never been able to track down the problem without having used $system.OBJ.* and an argumentless zwrite! IMHO, they are invaluable tools for when it comes to debugging COS and CSP pages.

Clone this wiki locally