There are times when an error is encountered in a program, particularly one that is running in batch, which I do not want to "error out" with a message. I want to cope with the error within the program, flag that there was an error so that I can resolve the issue later, and continue processing. If I can generate a program dump when the error occurs I have all the information I need to be able to fix it. CL's DMPCLPGM command and RPG's DUMP operation code do that without the need to break for an error message, producing the same output as if I had answered an error message with an option "D".
I am surprised at the number of IBM i developers who are unaware that it is possible to produce a dump without an error message. I would prefer to cope with errors and produce a dump within a program rather than receiving a 2 AM phone call from the night operator telling me that there is an error that is holding everything else up, and what am I going to do about it?
This post is not going to describe how to write code to handle all errors, or how to persuade the night operator it is not your turn to be called. It is going to describe how to produce those dumps, in three sections:
Spool file considerations
Dumps are output to the spool file, QPPGMDMP, which are sent to the QEZDEBUG output queue. Over time the dumps I am interested in can become lost amongst others in the same output queue. To make it easier for myself I always override these dumps to another output queue. I created an output queue called DUMPS, in the library QGPL, and then I override the spool file to there. This can be achieved by simply adding this to the first CL program of every job I want to generate these dumps for:
OVRPRTF FILE(QPPGMDMP) OUTQ(QGPL/DUMPS) + MAXRCDS(*NOMAX) OVRSCOPE(*JOB)
CL's DMPLCPGM command
The DMPCLPGM command has no parameters. I always use it in conjunction with an Override Printer file, OVRPRTF, command to give the spool file it a more meaningful name, the program's name.
01 PGM 02 DCL VAR(&PGM) TYPE(*CHAR) LEN(10) VALUE('TESTPGM1') 03 CPYF FROMFILE(TESTFILE) TOFILE(TESTFILEH) + MBROPT(*ADD) 04 MONMSG MSGID(CPF0000) EXEC(CALLSUBR SUBR(ERROR)) /*==========================================================*/ 05 SUBR SUBR(ERROR) 06 OVRPRTF FILE(QPPGMDMP) OUTQ(QGPL/DUMPS) + MAXRCDS(*NOMAX) SPLFNAME(&PGM) + OVRSCOPE(*CALLLVL) 07 DMPCLPGM 08 ENDSUBR /*==========================================================*/ 09 ENDPGM
If the Copy File command, CPYF on line 3, produces an error it is handled by the Monitor Message command, MONMSG on line 4. The MONMSG calls the subroutine "ERROR". If you have not encountered subroutines in CL you should read the post here.
The subroutine overrides the printer file, line 6, and then produces the dump, line 7. At the end of the subroutine the logic returns to where the subroutine was called.
I did not put the MONMSG at the top of the program to act as a "catch all" for errors as the only command you can enter in the EXEC parameter is GOTO. In my opinion using a GOTO makes for a lot of extra code to return to place I came from in the program. This not an issue when I use a subroutine.
RPG's DUMP operation code
The Dump operation code comes with an extender (A). If the extender is used then the dump will always happen. If it is not used then DEBUG(*YES) has to be used in the Control Options/Header Specifications for the dump to happen. As I like to keep things simple I use DUMP(A), and not debug in the Control Options.
I find it easier to find the line that is in error when the program uses the source statement numbers, rather than the ones generated at compile time. This can easily designated by using Control Options/Header Specifications option *SRCSTMT:
ctl-opt option(*srcstmt) ;Or
I will use the Dump operation anywhere where I think that it can identify in the problem is with data. A good example would be a divide by zero. This can easily be caught using a MONITOR group, the example below shows that when there is an error in the division, line 5, that a dump is performed, line 7, and a default is given to the result field, line 8. If you have not use these group you should read MONITOR for errors in RPG.
01 dcl-s a packed(3) inz(1) ; 02 dcl-s b like(a) ; 03 dcl-s c packed(7:4) ; 04 monitor ; 05 c = a/b ; 06 on-error 102 ; //Divide by 0 07 dump(a) ; 08 c = 1 ; 09 endmon ;
A full list of all the status codes you can use can be found on the IBM website:
Another common error is when numeric or alphanumeric fields are moved into date, time, or timestamp fields. In the example below when the field "TestFld" is tested it is found to be invalid, line 3, a dump is perform, line 5, and then today's date is moved into "DateFld", line 6, as a default value.
01 dcl-s TestFld zoned(8) inz(0) ; 02 dcl-s DateFld date ; 03 test(de) *iso TestFld ; 04 if %error ; 05 dump(a) ; 06 DateFld = %date() ; 07 else ; 08 DateFld = %date(TestFld:*iso) ; 09 endif ;
I can use the error operation extender with file operations and then dump the error. In the example below I have tried to read a file before it has been opened.
01 dcl-f TESTFILE usropn ; 02 read(e) TESTFILER ; 03 if (%error) ; 04 dump(a) ; 05 endif ; 06 open TESTFILE ;
All of these examples produce its own QPPGMDMP spool file, and as I would have overridden the printer file at the start of the job, in the output queue DUMPS.
When I first sign on to the IBM i I check the DUMPS output queue for any spool files created by the day-end jobs. With the dumps I can analyze the database looking for bad data that caused the error, or determine what changes need to be made programs to accommodate unexpected data scenarios.
You can learn more about these on the IBM website:
This article was written for IBM i 7.2, and should work for earlier releases too.