Informix 4GL Application Error Logging

Published on July 1, 1993 by Lester Knutsen

First published in the Washington Area Informix User Group Newsletter
Volume 3, No. 4 - July 1993

In every major application that I have developed, a requirement has been to have some way of logging what is going on inside the program. There are three types of application logging I like to use. The first type is to log what the user is doing so that when a user calls with a question or problem it is easier to figure out what went on. This is also a good way to find out what actually gets used in an application and what does not get used. The second type is to log all errors within the program. The third type is to log more detailed debugging information while a program is under development. The log could be to a file, to a network message system, or to a backup device. The log must contain the 4GL module name, the version, the line number in the source code that caused the error, and a message.

Informix 4GL has a function to write to a log file. First you need to create an error log with the function startlog("logname"). Then the function errorlog() can be used to save SQL error messages, informational messages about the application, and debugging messages. Informix 4GL will automatically write to an open error log the Informix error that causes a program to abort, unless you have the statement "whenever error continue" in your code. The log will include the source code line number and module name. However, because the program aborts, it does not allow you to do any clean-up that may be required. I have created a function called dtlog which includes the same information, and by setting the option "whenever error continue" in your code it allows your program to handle and recover from errors. The function uses features of the Unix Source Code Control System (SCCS) to write a module name, version and line number to the log file. The following is a description of the function and some examples.

When the function dtlog is called, a number (code), a string containing some SCCS information and a programmer supplied message is passed. With the SCCS information you can get the module name, SCCS version, and line number in the source file that generated the function call. The following are examples of a line calling my error logging function.

# error logging function when checked out of SCCS for editing (get -e filename)
call dtlog( code, "MOD:%M% REL:%I% LINE:%C% :",message )

# error logging function when checked out of SCCS for compiling ( get filename)
call dtlog( code, "MOD:dtlog.4gl REL:1.5 LINE:52 :",message )

There are three types of message calls to the function dtlog which writes the log file. The first argument passed is a number code for the type of message. The first type of message is a program error. If the number is negative, then it is assumed it is an Informix error message. By replacing the variable "code" in the above function with the SQL error code (sqlca.sqlcode) or status an error message will always print in the log if there is an error, and nothing will print if there is no error. The second type of message is informational. I use the number 1000 for messages that must be written to the log to indicate what is happening in the program. The third type of message is for debugging only. I use the number 0 to code messages that only get written to the log when debugging is turned on. To turn debugging on or off, one variable in the function, debug_flag, needs to be changed.

The second argument is a string containing the SCCS module name, version and line number. When the source code is checked out of SCCS using the get command, the %M% is replaced by the module file name, the %I% by the SCCS version, and the %C% by the line number within the file. This allows you to quickly find the file and line number that caused an error. See your Unix documentation for more information on SCCS.

The third argument is a user supplied message. This allows you to put messages like "preparing.." or "open form new.frm" in the log file to track what you are doing.

The following are some examples of how this function can be used. I like to start and end a program with a message to the log saying that the program started or ended with a function call like:

call dtlog(1000,"MOD:%M% REL:%I% LINE:%C% :","Starting Program")

After every SQL statement I put a function call to check for errors in addition to whatever error handling I have in the program. If debugging is turned off these will only print if there is an error.

call dtlog(status,"MOD:%M% REL:%I% LINE:%C% :","SQL Error ")
or
call dtlog(sqlca.sqlcode,"MOD:%M% REL:%I% LINE:%C% :","SQL Error ")

For debugging, you can put the following type of statement anywhere in your program that a message would be helpful. It will print only when debugging is turned on.

call dtlog(0,"MOD:%M% REL:%I% LINE:%C% :","Debugging Message")

This is the Informix 4GL code for the function:

#############################################################################
# Copyright 1993 Advanced DataTools Corporation
# Module: %W% Date: %D%
# Author: Lester B. Knutsen
# Description: General Informix 4GL Logging function
#############################################################################
function dtlog(code,relid,msg)
define
    code integer, # message type
    relid char(40), # SCCS filename, release and line number
    msg char(60), # application message passed to function
    msgline char(200), # message output to log
    debug_flag integer # set level of error logging
whenever error continue # keep going if there is an error

# set the level of debugging for messages to appear in the log
# one of the following must be uncommented
let debug_flag = true     # turn on debugging - all messages will
                          # appear in the log
#let debug_flag = false   # turn off debugging - only sql error
                          # messages or messages when code is 1000
                          # will appear in the log

case
    when ( code = 1000 ) # always write messages to the log
        let msgline = "MESSG: ",code using "------& ", relid ,
        msg clipped
        call errorlog(msgline)
        return
    when ( code < 0 )    # always write errors to the log 
        let msgline = "ERROR: ",code using "------& ", relid , 
        msg clipped, "\n", err_get(code) 
        call errorlog(msgline) 
        return 
    when ( code >= 0 and debug_flag = true ) # only when debugging
        let msgline = "DEBUG: ",code using "------& ", relid ,
        msg clipped
        call errorlog(msgline)
end case
end function

#############################################################################
# this is an example Informix 4GL program showing how you could use the functions

main
    whenever error continue # keep going if there is an error
    call startlog("program.log") # start the error log

    # example that will always create a message to the log
    call dtlog(1000,"MOD:%M% REL:%I% LINE:%C% :","Message 1 - Starting Program")

    # example that will only create a message if debugging is true
    Call dtlog(0,"MOD:%M% REL:%I% LINE:%C% :","Message 2 - Debugging Message")

    # example that will only create a message if their is an error or debugging is on
    call dtlog(status,"MOD:%M% REL:%I% LINE:%C% :","Message 3 - Error ")
    # or
    call dtlog(sqlca.sqlcode,"MOD:%M% REL:%I% LINE:%C% :","Message 3 - Error ")

    # example that will always create a message to the log
    call dtlog(1000,"MOD:%M% REL:%I% LINE:%C% :","Message 4 - Tracking Message")
end main