Introduction
Caché, like many systems, provides a mechanism that calls one or more user-written routines when certain noteworthy events happen. The processing for each event is usually treated as a subroutine of the system itself. In Caché there are two such routines: ^%ZSTART and ^%ZSTOP. Each defines one or more labels from a set of four pre-defined by the system. These labels become entrypoints into each routine that are called when that class of events happens:
The appropriate label is called in ^%ZSTART if the activity is beginning, and in ^%ZSTOP if is it ending.
These entrypoints, though they function as subroutines of Caché, are not intended to do complex calculations or run for long periods of time. Long calculations or potentially long operations like network accesses will delay the completion of the activity until the called routine returns. In this case, users may take a long (elapsed) time to login, or JOB throughput may be curtailed because they take too long to start.
Design Considerations
Because ^%ZSTART and ^%ZSTOP run in a somewhat restricted environment, the designer must keep several things in mind, namely:
Enabling %ZSTART and %ZSTOP
^%ZSTART and ^%ZSTOP must reside (and therefore be compiled) in the %SYS namespace. Their mere presence is not sufficient, however, for Caché to automatically begin using them. Instead, individual entrypoints are enabled or disabled via the Configuration Manager.
Once the routines have been designed, developed, compiled, and are ready to be tested, individual entrypoints may be enabled through the Configuration Manager. Start the Configuration Manager and select the Advanced tab. In the panel that appears, expand the Startup item, and then expand Enable User Code. A list of eight items is displayed. The list corresponds to all four combinations of event classes with either activity start or activity end, namely:
Each of these items will be marked as being enabled (= Yes) or disabled (= No). Their setting can be changed by highlighting the item and then selecting the Change option. A dialog box opens with a check box indicating whether the item is enabled (checked) or disabled (unchecked). If the setting is changed, you apply it by clocking OK.
When exiting the Configuration Manager, you are given an opportunity to apply the settings or discard them. In some cases, it is necessary to restart Caché in order to have the setting take effect.
As an illustration, suppose we wish to have something every time a background process starts. The appropriate entrypoint, JOB^%ZSTART, will be invoked by Caché when ALL the following are true:
Debugging ^%ZSTART and ^%ZSTOP
The opportunities for debugging ^%ZSTART and ^%ZSTOP in their final environment are very limited. If an error occurs, the error message that would be written to the TERMINAL in an interactive session is instead directed to the operator console log. This file is “cconsole.log” and is found in the Caché Manager's directory.
The message indicates the reason for failure and location where the error was detected. This may be different from the place where the error in the program logic or flow actually occurred. The developer is expected to deduce the nature and location of the error from the information provided, or modify the routine so that future tests provide more evidence as to the nature of the error.
It is important to note that, except in extreme failure circumstances, the Configuration Manager will start and run even though other Caché services may not be available. This means that it is possible to recover from and analyze many error situations by disabling the use of the offending function and restarting Caché.
To deactivate one or more of the entrypoint calls:
  1. Start the Configuration Manager.
  2. Choose the Advanced tab.
  3. Expand the Startup item and then Enable User Code.
  4. Change individual options by selecting them and choosing Change.
  5. When all of the desired changes have been made, click OK.
In some cases, it will be necessary to restart Caché in order to have the setting take effect.
Remember that ^%ZSTART and ^%ZSTOP (as well as any supporting routines) are stored persistently. To remove all traces of them:
  1. Start Explorer.
  2. Expand the %SYS namespace.
  3. Select Routines.
  4. In the right panel (Contents of 'Routines'), scroll down to the routine you wish to remove.
  5. Select it.
  6. Choose Delete from the File menu. Confirm the deletion by selecting Yes in the subsequent dialog box.
Note:
It is strongly recommended that you disable the entry point options via the Caché Configuration Manager BEFORE deleting the routines. If the Configuration Manager warns that a restart of Caché is needed for them to take effect, do this as well before proceeding. This guarantees that none of the entrypoints are being executed while they are being deleted.
An Example Implementation
The following example demonstrates a simple log for tracking system activity. It consists of a common utility for writing log entries called ^%ZSSUtil as well as separate routines for ^%ZSTART and ^%ZSTOP. Since the ^%ZSTOP routine mirrors many of the activities of ^%ZSTART, most of the discussion is about ^%ZSTART and ^%ZSSUtil. All three routines are shown, however.
Note:
The code in these examples has been written to aid understanding, not to illustrate clever usages of ObjectScript or be “as tight as possible.”
Routine: ^%ZSSUtil
This routine has two public entrypoints. One is used to write a single line to the operator console log. The other is used to write a list of name-value pairs to a local log file. Both the operator console log and the local log file reside in the Caché Manager directory. The name of this directory is returned by the ManagerDirectory method of the %Library.File class.
%ZSSUtil ;
    ; this routine packages a set of subroutines 
    ; used by the %ZSTART and %ZSTOP entrypoints
    ; 
    ; does not do anything if invoked directly
    quit
    
#Define Empty ""
#Define OprLog 1
    
WriteConsole(LineText) PUBLIC ;
    ; write the line to the console log
    ; by default the file cconsole.log in the MGR directory
    new SaveIO
    
    ; save the current device and open the operator console
    ; set up error handling to cope with errors
    ; there is little to do if an error happens
    set SaveIO = $IO
    set $ZTRAP = "WriteConsoleExit"
    open $$$OprLog
    use $$$OprLog
    ; we do not need an "!" for line termination
    ; because each WRITE statement becomes its 
    ; own console record (implicit end of line)
    write LineText
    ; restore the previous io device
    close $$$OprLog
    ; pick up here in case of an error
WriteConsoleExit ;
    set $ZTRAP = ""
    use SaveIO
    quit
    
WriteLog(rtnname, entryname, items) PUBLIC ;
    ; write entries into the log file
    ; the log is presumed to be open as 
    ; the default output device
    ; 
    ; rtnname: distinguishes between ZSTART & ZSTOP
    ; entryname: the name of the entrypoint we came from
    ; items: a $LIST of name-value pairs
    new ThisIO, ThisLog
    new i, DataString
    
    ; preserve the existing $IO device reference
    ; set up error handling to cope with errors
    ; there is little to do if an error happens
    set ThisIO = $IO
    set $ZTRAP = "WriteLogExit"

    ; construct the name of the file
    ; use the month and day as part of the name so that 
    ; it will create a separate log file each day
    set ThisLog = "ZSS"
                _ "-"
                _ $EXTRACT($ZDATE($HOROLOG, 3), 6, 10)
                _".log"
    
    ; and change $IO to point to our file
    open ThisLog:"AWS":0
    use ThisLog
    
    ; now loop over the items writing one line per item pair
    for i = 1 : 2 : $LISTLENGTH(items)
    {
        set DataString = $LISTGET(items, i, "*MISSING*")
        if ($LISTGET(items, (i + 1), $$$Empty) '= $$$Empty)
        {
            set DataString = DataString
                           _ ": "
                           _ $LISTGET(items, (i + 1))
        }
        write $ZDATETIME($HOROLOG, 3, 1),
              ?21, rtnname,
              ?28, entryname,
              ?35, DataString, !
    }
    
    ; stop using the log file and switch $IO back
    ; to the value saved on entry
    close $IO
    ; pick up here in case of an error
WriteLogExit ;
    set $ZTRAP = ""
    use ThisIO
    quit
Explanation
Label: ^%ZSSUtil
This routine (as well as the others) begins with a QUIT command so that it is benign if invoked via
    do ^%ZSSUtil
The #DEFINE sequence cosmetically provides named constants in the body of the program. In this instance, it names the empty string and the device number of the operator console log.
Label: WriteConsole^%ZSSUtil
The entrypoint is very simple. It is designed for low volume output, and as a minimally intrusive routine to use for debugging output.
It takes a single string as its argument and writes it to the operator console log. However, it must take care to preserve and restore the current $IO attachment across its call.
One aspect of the console device is that each item sent to the device results in a separate record being written to the console log. Thus,
    WRITE 1, 2, 3, !
results in four records being written. The first three consist of a single digit and the fourth is a blank line. If multiple items are desired on a line, it is the responsibility of the caller to concatenate them into a string.
Label: WriteLog^%ZSSUtil
This is the more complex of the two routines. It can be called by any entrypoint within ^%ZSTART or ^%ZSTOP. The information needed to report on whose behalf it is operating is supplied by the first two arguments. The third argument is a $LIST of name-value pairs to be written to the log.
This entrypoint starts by constructing the name of the file it will use. To make log management easier, the name contains the month and day when the routine is entered. Therefore, calls to WriteLog will create a new file whenever the local time crosses midnight. Since the name is determined only at the time of the call, all the name-value pairs passed as the argument will be displayed in the same file.
Once the name has been constructed, the current value of $IO is saved for later use and the output device is switched to the named log file. The parameters used for the OPEN command insure that the file will be created if it is not there. The timeout of zero indicates that Caché will try a single time to open the file and fail if it cannot.
Once the file has been opened, the code loops over the name value pairs. For each pair, the caller routine name and the entrypoint name are written followed in the line by the name-value pair. (If the value part is the empty string, only the name is written.) Each pair occupies one line in the log file. The first three values on each line are aligned so they appear in columns for easier scanning.
When all the pairs have been written, the log file is closed, the previous value $IO is restored and control returns to the caller.
Routine: ^%ZSTART
This routine contains the entrypoint actually invoked by the Caché. It uses the services of ^%ZSSUtil just described. All the entrypoints act more or less the same, they place some information in the log. The SYSTEM entrypoint has been made slightly more elaborate than the others. It places information in the Operator console log as well.
%ZSTART ; User startup routine. 
 
#Define ME "ZSTART"
#Define BgnSet "Start"
#Define Empty ""

    ; cannot be invoked directly
    quit
 
SYSTEM ;
    ; Cache starting
    new EntryPoint, Items
     
     set EntryPoint = "SYSTEM"
    
     ; record the fact we got started in the console log
     do WriteConsole^%ZSSUtil((EntryPoint
                               _ "^%"
                               _ $$$ME
                               _ " called @ "
                               _ $ZDATETIME($HOROLOG, 3)))
    
    ; log the data accumulate results
     set Items = $LISTBUILD($$$BgnSet, $ZDATETIME($HOROLOG, 3),
                           "Job", $JOB,
                           "Computer", $ZUTIL(110),
                           "Version", $ZVERSION,
                           "StdIO", $PRINCIPAL,
                           "Namespace", $ZUTIL(5),
                           "CurDirPath", $ZUTIL(12),
                           "CurNSPath", $ZUTIL(12, ""),
                           "CurDevName", $ZUTIL(67, 7, $JOB),
                           "JobType", $ZUTIL(67, 10, $JOB),
                           "JobStatus", $ZHEX($ZJOB),
                           "StackFrames", $STACK,
                           "AvailStorage", $STORAGE,
                           "UserName", $ZUTIL(67, 11, $JOB))
    do WriteLog^%ZSSUtil($$$ME, EntryPoint, Items)
    
    quit
    
LOGIN ;
    ; a user logs into Cache (user account or telnet)
    new EntryPoint, Items
    
    set EntryPoint = "LOGIN"
     set Items = $LISTBUILD($$$BgnSet, $ZDATETIME($HOROLOG, 3))
    do WriteLog^%ZSSUtil($$$ME, EntryPoint, Items)
    quit
    
JOB ;
    ; JOB'd process begins
    new EntryPoint, Items
    
     set EntryPoint = "JOB"
     set Items = $LISTBUILD($$$BgnSet, $ZDATETIME($HOROLOG, 3))
    do WriteLog^%ZSSUtil($$$ME, EntryPoint, Items)
    quit
     
CALLIN ;
    ; a process enters via CALLIN interface
    new EntryPoint, Items
    
     set EntryPoint = "CALLIN"
     set Items = $LISTBUILD($$$BgnSet, $ZDATETIME($HOROLOG, 3))
    do WriteLog^%ZSSUtil($$$ME, EntryPoint, Items)
    quit
Explanation
Label: ^%ZSTART
As noted in the discussion of ^%ZSSUtil, this routine begins with a QUIT command so that it is benign if invoked as a routine rather than beginning its execution properly at one of its entrypoints.
This routine also defines named constants (as macros) for its own name, a starting string and the empty string.
Label: SYSTEM^%ZSTART
Since ^%ZSSUtil takes care of most of the detail of writing to the log file(s), this routine consists mostly of collecting information and then invoking the proper entrypoint in ^%ZSSUtil. It constructs a string giving its routine name, entrypoint, and the datetime it was invoked. Then it calls WriteConsole^%ZSSUtil to place it in the operator console log.
Afterward, it constructs a list of name-value pairs that it wishes to be displayed. It passes this to WriteLog^%ZSSUtil to place into the local log file. Then it returns to its caller.
Labels: LOGIN^%ZSTART, JOB^%ZSTART, CALLIN^%ZSTART
Unlike the SYSTEM entrypoint, these do not place any information in the operator console log. They construct a short list of items, enough to identify that they were invoked, and then use WriteLog^%ZSSUtil to record it.
Routine: ^%ZSTOP
This routine contains the entrypoint actually invoked by Caché. Like ^%ZSTART, it too uses the services of ^%ZSSUtil. The entrypoints have the same names as those in %ZSTART and serve similar functions. As with ^%ZSTART, only the SYSTEM entrypoint writes to the operator console log. All routines note their activity in the local log.
%ZSTOP ; User shutdown routine. 
 
#Define ME "ZSTOP"
#Define EndSet "End"
#Define Empty ""

    ; cannot be invoked directly
    quit
 
SYSTEM ; Cache stopping
    new EntryPoint
    
    set EntryPoint = "SYSTEM"
     ; record termination in the console log
     do WriteConsole^%ZSSUtil((EntryPoint
                               _ "^%"
                               _ $$$ME
                               _ " called @ "
                               _ $ZDATETIME($HOROLOG, 3)))
     ; write the standard log information
    do Logit(EntryPoint, $$$ME)
    quit
    
LOGIN ; a user logs out of Cache (user account or telnet)
    new EntryPoint
    
    set EntryPoint = "LOGIN"
    do Logit(EntryPoint, $$$ME)
    quit
    
JOB ; JOB'd process exits. 
    new EntryPoint
    
    set EntryPoint = "JOB"
    do Logit(EntryPoint, $$$ME)
    quit
     
CALLIN ; process exits via CALLIN interface. 
    new EntryPoint
    
    set EntryPoint = "CALLIN"
    do Logit(EntryPoint, $$$ME)
    quit
    
Logit(entrypoint, caller) PRIVATE ;
    ; common logging for exits
    
    new items
    
     set items = $LISTBUILD($$$EndSet, $ZDATETIME($HOROLOG, 3))
    do WriteLog^%ZSSUtil(caller, entrypoint, items)
    quit
Explanation
This routine mimics the functions of ^%ZSTART. Refer to the description, example, and explanation of ^%ZSTART for information.