I've been searching all over the internet (and this site) looking for a good way to generically handle errors within a CL program. Everything's seems very vauge to me.
What I'm looking to do is something like this:
CHGSPLFA FILE(&SPOOLFILE) SAVE(*NO)
MONMSG MSGID(CPF0000) EXEC('SEND EMAIL WITH ERROR MESSAGE DETAILS TO A USER AND EXIT PROGRAM')
I'd like to know the line it failed at, and all of the message details in the email.
I know you can do EXEC(GOTO CMDLBL(ERROR)) and use SNDDST to send the email. I'm not really sure how to pull the information our of the exception thrown.
I'm so used to taking care of business with try{} catch(e){} in other languages that I never really learned about proper error handling in CL.
Thanks in advance for the help!
Dave
Software/Hardware used:
CL, ILE, iSeries, V5R4
ASKED:
February 22, 2011 9:10 PM
UPDATED:
February 23, 2011 4:54 PM
The handling of errors in CL (or any compiled language under the OS/400 scheme) essentially requires a basic understanding of how messages are handled. Exceptions are generally signaled through the sending of messages. If you receive those messages, you can extract almost anything you’d ever need to know.
As an introduction, here is a trivial CL program that causes an error and then receives the two relevant messages:
pgm dcl &KeyVar *char 4 dcl &MsgTxt *char 132 dcl &MsgLen *dec ( 5 0 ) dcl &MsgDta *char 512 dcl &MsgDtaLen *dec ( 5 0 ) dcl &MsgId *char 7 dcl &Sender *char 720 call ABCDXYZMN monmsg ( cpf0000 ) exec( callsubr HandleErr ) return subr HandleErr rcvmsg msgtype( *LAST ) rmv( *NO ) + keyvar( &KeyVar ) + msg( &MsgTxt ) msglen( &MsgLen ) + msgdta( &MsgDta ) msgdtalen( &MsgDtaLen ) + msgid( &MsgId ) + sender( &Sender ) + senderfmt( *LONG ) dmpclpgm rcvmsg msgtype( *PRV ) msgkey( &KEYVAR ) rmv( *NO ) + keyvar( &KeyVar ) + msg( &MsgTxt ) msglen( &MsgLen ) + msgdta( &MsgDta ) msgdtalen( &MsgDtaLen ) + msgid( &MsgId ) + sender( &Sender ) + senderfmt( *LONG ) dmpclpgm endsubr endpgmThe program simply calls another program named “ABCDXYZMN”. Assuming that no such program exists in the library list, that will cause an error.
The general error will be CPF0001, “Error found on CALL command.” That’s an error relating to almost any kind of command on the system, and it indicates that the command simply can’t complete as compiled. There’s nothing wrong with the syntax; it just won’t work. The CPF0001 is sent as a fairly non-specific *ESCAPE message. The *ESCAPE message is the signaling of failure.
But before that message was sent, another one was sent — CPD0170, “Program ABCDXYZMN in library *LIBL not found.” That was sent as a *DIAG (Diagnostic) message. Diagnostic messages carry some details about exactly what went wrong.
There might be multiple *DIAG messages accompanying an *ESCAPE message. In this case there is only one. In practice, you might have the second RCVMSG in a loop for the Diagnostics in order to receive all of them.
Now, the example doesn’t actually do anything except retrieve message attributes in some variables and then run dumps. Without knowing what you’ll do, there’s no way to really code any actions. But you can compile and run the program, and then compare the dumped variables against the help text of the RCVMSG parms. Also, compare the values against message details that you might see in the joblog.
You should be able to recognize stuff. For example, if you look at positions 645-648 of the &Sender variable in the dump, you should see that it matches the “To statement” statement number in the message details for the message in the joblog.
There is too much information about sending and receiving messages. A truly “generic” handling of them could probably be done, but specific handling usually works out better.
Try running the above program. Review the info that gets captured. Compare it against the RCVMSG help text for the parms that I used (and note that others are available). See what comes to mind.
Then ask followup questions for any details that aren’t clear.
Keep in mind that *ESCAPE messages signal failure of something. I’ve had circumstances where “failures” mean that attempts to run any additional processing simply cause additional errors. ‘Generic’ handling of errors just might not work in a failure scenario.
Tom
OK. Thanks for the info Tom and Charlie.
I’ve come up with some code based on your posted code Tom:
PGM DCL VAR(&KEYVAR) TYPE(*CHAR) LEN(4) DCL VAR(&MSGTXT) TYPE(*CHAR) LEN(132) DCL VAR(&MSGLEN) TYPE(*DEC) LEN(5 0) DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(512) DCL VAR(&MSGDTALEN) TYPE(*DEC) LEN(5 0) DCL VAR(&MSGID) TYPE(*CHAR) LEN(7) DCL VAR(&SENDER) TYPE(*CHAR) LEN(720) DCL VAR(&EMAIL) TYPE(*CHAR) LEN(60) + VALUE(email@email.COM) DCL VAR(&N) TYPE(*CHAR) LEN(3) VALUE(':/N') DCL VAR(&P) TYPE(*CHAR) LEN(3) VALUE(':/P') /*Let's call this a hard error. We want to leave the program*/ CALL PGM(ABCDXYZMN) MONMSG MSGID(CPF0000) EXEC(CALLSUBR HANDLEERR) RETURN SUBR SUBR(HANDLEERR) RCVMSG MSGTYPE(*LAST) RMV(*NO) KEYVAR(&KEYVAR) + MSG(&MSGTXT) MSGLEN(&MSGLEN) MSGDTA(&MSGDTA) + MSGDTALEN(&MSGDTALEN) MSGID(&MSGID) + SENDER(&SENDER) SENDERFMT(*LONG) SNDDST TYPE(*LMSG) TOINTNET((&EMAIL) (*NONE *CC)) + DSTD('FINANCIAL SERVICES IMAGING ERROR') + LONGMSG('MSGID:' |> &MSGID |> &P || 'Message + Text:' |> &MSGTXT |> &P || 'Message Data:' |> + &MSGDTA |> &P || 'END ERROR') AUTHOR(DAVEC) + DISCLOSE(*YES) ALWX400CNV(*NO) RCVMSG MSGTYPE(*PRV) MSGKEY(&KEYVAR) RMV(*NO) + KEYVAR(&KEYVAR) MSG(&MSGTXT) MSGLEN(&MSGLEN) + MSGDTA(&MSGDTA) MSGDTALEN(&MSGDTALEN) + MSGID(&MSGID) SENDER(&SENDER) SENDERFMT(*LONG) SNDDST TYPE(*LMSG) TOINTNET((&EMAIL) (*NONE *CC)) + DSTD('FINANCIAL SERVICES IMAGING ERROR') + LONGMSG('MSGID:' |> &MSGID |> &P || 'Message + Text:' |> &MSGTXT |> &P || 'Message Data:' |> + &MSGDTA |> &P || 'END ERROR') AUTHOR(DAVEC) + DISCLOSE(*YES) ALWX400CNV(*NO) ENDSUBR ENDPGMHere’s my new questions: