Enabling custom End User tracing in your application

Date:8th December 2009
Product/Release:Visual LANSA SP5 (RDMLX)
Abstract:How make use of a Visual LANSA tracing feature in RDMLX applications
Submitted By:LANSA Technical Support

Visual LANSA has a tracing feature that you can embed in your own RDMLX applications. This can be added to your source during development or included when trying to determine the cause of complex issues where debugging is simply too longwinded or complicated.

Prim_App.iTraceHandler Interface

Firstly, you need to create an object to act as your trace handler. An example and explanation of a typical trace handler is included later. This object implements the iTraceHandler interface, which has the following methods.

  • Initialize – Called when the trace handler is installed as the active trace handler for the application
  • Terminate – Called whenever the trace handler is uninstalled
  • TraceMessage – Called whenever a either the TraceMessageText or TraceMessageData methods are called on Sys_Appln
  • TracingState – Called whenever a either the TraceMessageText or TraceMessageData methods are called on Sys_Appln

Implementation of this interface means that a component can be installed as the system trace handler at run time.

Using the Trace Handler

Once you have defined your trace handler, in this case as a component called Tracing, it can be added to your application. This can be done as part of the initialization with a simple Define_Com as below

Define_Com Class(#Tracing) name(#TraceHandler)

If you are using the handler provided in the example, you need do nothing else. It is a self contained object that will set itself as the system trace handler if a suitable trigger condition is satisfied; in this case the existence of TracingOn.txt in the system source folder.You are now ready to start adding tracing commands to your application source.

There are two methods available on Sys_Appln; TraceMessageText & TraceMessageData

TraceMessageText has 4 variant input parameters. When used the TraceMessage method in the handler will be executed once for each of the variables specified.

#Sys_Appln.TraceMessageText(#Empno #Givename #Surname)

TraceMessageData has a Source parameter and a further 9 Value parameters. The Value parameters will be substituted in to the source parameters as if the Substitute intrinsic were being used.

#Sys_Appln.TraceMessageText(“Employee &1 Name &2 &3” #Empno #Givename #Surname)

Trace Handler Guidelines and Performance

Quite how many trace statements to include and what data to output is entirely up to you. Clearly writing a trace statement after every command is counterproductive and will produce enormous trace files, but then sporadically placed commands with no real structure will be of little use.

However, there are moments during the execution of an application that might be considered typical points of interest, for example, after a record has been read or before an update. If a subroutine or method is about to be called to manipulate some data, it may be beneficial to trace values before and after.

You might also consider flagging trace entries with some form of code to identify the nature of the processing going on at the time. This will greatly improve your understanding of the output without having to necessarily be overly familiar with the source itself.

#Sys_Appln.TraceMessageText(“UPDATE Employee &1 Name &2 &3” #Empno #Givename #Surname)

The trace feature is designed specifically such that the commands can be left in place. If the trigger for tracing is not in place, no trace handler is installed. The result is that when the call to the trace method is made there is nothing to do. This is equivalent to calling an empty method and while there must be a minor overhead it is negligible. Simple tests demonstrate that 100,000 trace statements add about a quarter of a second to the run time. If tracing is active, the same added about 45 seconds to the total execution time.

Example Use of Tracing

This is a simple form to demonstrate how the results of using both tracing options. It uses a trace handler defined from the sample supplied. To ensure that output is produced, create TracingOn.txt in the system folder (...\x_win95\x_lansa). Trace files will also be created in the system folder.

Function Options(*DIRECT)
Begin_Com Role(*EXTENDS #PRIM_FORM) Caption('Doubleclick an item to test the tracing') Clientheight(322) Clientwidth(626) Height(358) Left(213) Top(160) Width(642)
Define_Com Class(#PRIM_LTVW) Name(#List) Componentversion(2) Displayposition(1) Fullrowselect(True) Height(249) Keyboardpositioning(SortColumn) Left(0) Parent(#COM_OWNER) Showsortarrow(True) Tabposition(1) Top(0) Width(625)
Define_Com Class(#PRIM_LVCL) Name(#LVCL_3) Displayposition(1) Parent(#List) Source(#EMPNO)
Define_Com Class(#PRIM_LVCL) Name(#LVCL_4) Displayposition(2) Parent(#List) Source(#GIVENAME) Width(36)
Define_Com Class(#PRIM_LVCL) Name(#LVCL_5) Displayposition(3) Parent(#List) Source(#SURNAME) Width(41)
Define_Com Class(#PRIM_RDBN) Name(#TraceMessageData) Buttonchecked(True) Caption('Use TraceMessageData') Displayposition(2) Left(8) Parent(#COM_OWNER) Tabposition(2) Top(254) Width(153)
Define_Com Class(#PRIM_RDBN) Name(#TraceMessageText) Caption('Use TraceMessageText') Displayposition(3) Left(8) Parent(#COM_OWNER) Tabposition(3) Top(286) Width(153)
Define_Com Class(#Tracing) Name(#TraceHandler)

Evtroutine Handling(#com_owner.CreateInstance)
Set Com(#com_owner) Caption(*component_desc)
Clr_List Named(#List)
Select Fields(#List) From_File(Pslmst)
Add_Entry To_List(#List)
Endselect

Endroutine
Evtroutine Handling(#List.DoubleClick)
If (#TraceMessageData.ButtonChecked) #sys_appln.TraceMessageData( "Employee &1 Name &2 &3" #Empno #Givename #Surname )
Else
#sys_appln.TraceMessageText( #Empno #Givename #Surname )
Endif
Endroutine
End_Com

Example Trace Handler

This is a simple self contained trace handler. If a text file called TracingOn.txt exists in the ...\X_win95\lansa folder, the trace handler will install itself at run time and a trace file will be created. Any trace statements coded in to the application will be output.

Cut and paste the source in to a new reusable part and save to ensure all errors disappear.

Function Options(*DIRECT)
Begin_Com Role(*EXTENDS #PRIM_OBJT *implements #prim_app.iTraceHandler)
Define Field(#FilHandle) Type(*dec) Length(3) Decimals(0)
Define_Com Class(#prim_alph) Name(#Tab)

Evtroutine Handling(#Com_Owner.CreateInstance)
#Tab := (09).asChar
#Com_owner.InstallTracing
Endroutine

Mthroutine Name(InstallTracing) Help('Plug in the trace handler to the Application') Access(*private)
* This example uses a text file as the trigger to implement user tracing.
* You might also use a registry entry
If (#Com_owner.FileExists( (*Sys_dir + "TracingOn.txt") ))
* Set this object as the system help handler
#Sys_appln.TraceHandler <= #Com_owner
Endif
Endroutine

Mthroutine Name(Initialize) Options(*redefine) Access(*private)
#Com_owner.OpenTraceFile
Endroutine

Mthroutine Name(Terminate) Options(*redefine) Access(*private)
#Com_owner.CloseTraceFile
Endroutine

Mthroutine Name(TraceMessage) Help('Executed whenever #sys_appln.TraceMessageData or #sys_appln.TraceMessageText is used') Options(*redefine) Access(*private)
#Com_owner.WriteToFile( #ComponentName #Description #LineNumber 
#MessageText )
Endroutine

Mthroutine Name(OpenTraceFile) Help('Create a new trace outputfile') Access(*private)
Use Builtin(Stm_File_Open) With_Args(#Com_owner.GetNextFile Append N Y) To_Get(#FilHandle #io$sts)
Endroutine

Mthroutine Name(WriteToFile) Help('Write an entry in the trace output file') Access(*private)
Define_Map For(*Input) Class(#prim_alph) Name(#ComponentName)
Define_Map For(*Input) Class(#prim_alph) Name(#Description)
Define_Map For(*Input) Class(#prim_nmbr) Name(#LineNumber)
Define_Map For(*Input) Class(#prim_alph) Name(#MessageText)
Define_Com Class(#prim_Dat) Name(#Now)
#MessageText := #Now.now.AsLocalizedDateTime.AsString + #Tab + #ComponentName + #Tab + #LineNumber.asstring + #Tab + #MessageText
Use Builtin(Stm_File_Write) With_Args(#FilHandle #MessageText) To_Get(#io$sts)
Endroutine

Mthroutine Name(CloseTraceFile) Access(*private)
Use Builtin(Stm_File_Close) With_Args(#FilHandle) To_Get(#io$sts)
Endroutine

Mthroutine Name(TracingState) Options(*redefine)
#MessageTracingActive := True
Endroutine

Mthroutine Name(GetNextFile) Access(*Private)
Define_Map For(*Result) Class(#Prim_alph) Name(#Result)
Define_Com Class(#prim_nmbr) Name(#Extension)
Begin_Loop Using(#Extension)
#Result := *Sys_dir + "UserTrace" + "." + #Extension.asstring.rightAdjust( 3 "0" )
Leave If(*Not #Com_owner.FileExists( #Result ))
End_Loop
Endroutine

Mthroutine Name(FileExists) Access(*private)
Define_Map For(*input) Class(#prim_alph) Name(#Path)
Define_Map For(*result) Class(#prim_boln) Name(#Result)
Use Builtin(OV_FILE_SERVICE) With_Args(Check_File #Path) To_Get(#io$sts)
#Result := (#io$sts = OK)
Endroutine
End_Com