Now that we've figured out how to receive them, how do we remove them if we want to?
Last month, in "The API Corner: More on Message Handling," we looked at how to receive diagnostic messages (such as CPF3213 – Members for file more than maximum allowed) using the Receive Program Message (QMHRCVPM) API. The diagnostic message was needed in order to determine the underlying cause of an escape message (in our example, CPF7306 – Member not added) and then take the appropriate corrective action. The recovery action for CPF3213 was to change the file's MAXMBRS attribute to *NOMAX by using the Change Physical File (CHGPF) command and then adding the required member using the Add Physical File Member (ADDPFM) command. Both of these commands were run by the RPG application program using the Process Commands (QCAPCMD) API. However, after successfully adding the member, we noticed that the job log still contained the CPF3213 diagnostic message along with the two completion messages (CPC7303 – File SOMEFILE in library xxxxxx changed and CPC7305 – Member yyyyyy added to file SOMEFILE in xxxxxx) that were related to our handling of the initial CPF7306 error. Today's "API Corner" article looks at various options of how we can, if we want to, remove these messages.
In earlier articles, such as "The API Corner: Automating Recovery (or Keeping the Help Desk Out of the Loop)," we used the Remove Program Message (QMHRMVPM) API, documented here, but we never really looked at the API in any detail. The QMHRMVPM API has five required parameters and several optional parameter groups. These are the five required parameters (and the only parameters that we will be using today):
1 |
Call stack entry |
Input |
Char(*) or Pointer |
2 |
Call stack counter |
Input |
Binary(4) |
3 |
Message key |
Input |
Char(4) |
4 |
Messages to remove |
Input |
Char(10) |
5 |
Error code |
I/O |
Char(*) |
In the previously mentioned Automating Recovery article, we prototyped these five required parameters this way:
dRmvMsg pr extpgm('QSYS/QMHRMVPM')
d CallStackEntry 65535 const options(*varsize)
d CallStackCntr 10i 0 const
d MsgKey 4 const
d MsgsToRmv 10 const
d QUSEC likeds(QUSEC)
And then we called the API using this statement:
RmvMsg('*' :0 :' ' :'*ALL' :QUSEC);
The first two parameters, Call stack entry and Call stack counter, work together to identify where the message (or messages) to be removed can be found. The special value '*' for Call stack entry specifies that the base call stack entry to use for locating the message(s) is the caller of the QMHRMVPM API. The Call stack counter parameter provides a counter, relative to the Call stack entry parameter value, of where the message(s) can be found. A value of 0 for Call stack counter simply indicates that the Call stack entry parameter value is indeed where to find the message(s). So the first two parameters (* and 0) are telling the QMHRMVPM API that the message to be removed can be found in the program message queue of the API caller.
The next two parameters, Message key and Messages to remove, also work together and are used to identify the message (or messages) to be removed. In the Automating Recovery article, the respective values ' ' and *ALL instruct the QMHRMVPM API to remove all messages from the call stack entry identified by the Call stack entry and Call stack counter parameters.
The fifth parameter, QUSEC, is the standard API error code structure and, due to the setting of the API error code Bytes provided field in the sample program, indicate that any error in removing the message(s) should be returned as an escape message.
Returning now to the sample program of More on Message Handling, we could use this same approach for removing the CPF3213 (Members for file more than maximum allowed) message by inserting a call to the QMHRMVPM API within the main procedure as shown below.
select;
when MessageInfo.Common.QMHMI03 = 'CPF3213';
HdlCPF3213();
RmvMsg('*' :0 :' ' :'*ALL' :QUSEC);
other;
SndHardError();
endsl;
This approach will work because the HdlCPF3213 procedure, as coded, will return to the calling procedure only when the recovery logic has successfully run. That is, any failure within HdlCPF3213 will be returned as an escape message and, as the sample program does not have an active monitor for escape messages, the subsequent API call will never run.
This approach, however—removing the CPF3213 message within the main procedure—seems awkward to me. To my way of thinking, it would make more sense for the HdlCPF3213 procedure to remove the message when the HdlCPF3213 procedure has resolved the error condition. One possible method for the HdlCPF3213 procedure to remove this diagnostic message is shown here.
pHdlCPF3213 b
/free
Cmd = 'CHGPF FILE(SOMEFILE) MAXMBRS(*NOMAX)';
RunCmd(Cmd :%len(%trimr(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :QUSEC);
Cmd = 'ADDPFM FILE(SOMEFILE) MBR(' + PSDS.JobUsr + ')';
RunCmd(Cmd :%len(%trimr(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :ErrCde);
if ErrCde.Common.QUSBAVL > 0;
SndHardError();
endif;
RmvMsg('*' :1 :' ' :'*ALL' :QUSEC);
/end-free
pHdlCPF3213 e
As you can see above, we have added a call to the QMHRMVPM API to the HdlCPF3213 procedure. When calling the API within the HdlCPF3213 procedure, we have also changed the second parameter, Call stack counter, from the previously used value of 0 to the new value of 1. This change is necessary as the CPF3213 message was not sent to the HdlCPF3213 procedure (or call stack entry). Rather, this message was sent to the caller of HdlCPF3213, where, in the mainline code, we initially attempted to add a member to the file SOMEFILE. The 1 is instructing the QMHRMVPM API to remove all messages from the call stack entry associated with the caller of HdlCPF3213 (rather than HdlCPF3213 itself). If we had left the Call stack counter value at 0, then the QMHRMVPM API would not have removed the CPF3213 message (as it would not have been found). This approach—removing messages from the procedure's caller—while effective at removing the diagnostic message, does however have the potential for some undesirable side effects.
The main problem is that HdlCPF3213 is now removing all program messages found in its caller's program message queue or call stack entry. Right now, that isn't a problem as the only message being removed is the CPF3213, but what if six months from now…
- additional processing is added to the main procedure prior to clearing the member of file SOMEFILE?
- this additional logic includes the logging of message USR5678 to the job log?
- the message USR5678 is to be retained in the job log when the main program completes successfully?
By having the HdlCPF3213 procedure remove all messages from the program message queue of its caller, then whenever HdlCPF3213 successfully recovers from escape message CPF7306 and diagnostic message CPF3213, message USR5678, as an unintended side effect, will also be removed. Note that this removal of all messages in the caller's program message queue can be "just right" for a procedure named RemoveAllMyMessages. It's just that a procedure whose sole function is to recover from message CPF3213 should not, as a side effect, remove all of the caller's program messages. To avoid this problem, and still have the HdlCPF3213 procedure clean up the job log, we need to be more selective in terms of specifying what message is to be removed.
Critical Parameters: Message key and Messages to remove
This is where the Message key and Messages to remove parameters of the QMHRMVPM API come into play. In addition to the special value *ALL for removing all messages, the QMHRMVPM Messages to remove parameter can also be specified with the special value *BYKEY. *BYKEY indicates that the specific message identified by the Message key parameter, and only that message, is to be removed. This message key is a unique key value, assigned by the system, for each message found in the job log of a job. Note the qualifier "in the job log of a job." Duplicate message key values can occur across jobs; it is only within a given job that a message key value is unique. Now the question is "How do I get the message key value for the CPF3213 diagnostic message?" You may recall from last month's article, "More on Message Handling," that when receiving a message with the Receive Program Message (QMHRCVPM) API, format RCVM0100 returns quite a bit of information about the message being received. In particular, the message key is returned in format RCVM0100 at offset 21 or, if you are using the IBM QSYSINC-provided QRPGLESRC include member QMHRCVPM, field QMHMK03. So to have procedure HdlCPF3213 remove the CPF3213 message from the job log, given that we know the correct message key value to specify from our previous use of the QMHRCVPM API, we can call the QMHRMVPM API as shown below.
pHdlCPF3213 b
/free
Cmd = 'CHGPF FILE(SOMEFILE) MAXMBRS(*NOMAX)';
RunCmd(Cmd :%len(%trimr(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :QUSEC);
Cmd = 'ADDPFM FILE(SOMEFILE) MBR(' + PSDS.JobUsr + ')';
RunCmd(Cmd :%len(%trimr(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :ErrCde);
if ErrCde.Common.QUSBAVL > 0;
SndHardError();
endif;
RmvMsg('*' :0 :MessageInfo.Common.QMHMK03 :'*BYKEY' :QUSEC);
/end-free
pHdlCPF3213 e
When *BYKEY is specified for the Messages to remove parameter of the QMHRMVPM API, the first and second parameters, Call stack entry and Call stack counter, are ignored. As parameters are specified positionally, we have to specify something for these two parameters and have used '*' and 0 for lack of anything better, but these values are not used in locating the message to be removed. The qualified name MessageInfo.Common.QMHMK03 is sufficient for the HdlCPF3213 procedure to uniquely identify the message to be removed.
The HdlCPF3213 procedure is now removing the CPF3213 diagnostic message when a member is successfully added to file SOMEFILE. It was noted earlier, though, that two completion messages (CPC7303 and CPC7305) are also in the job log as a result of running the CHGPF and ADDPFM commands during the error recovery logic of HdlCPF3213. These messages were sent to the HdlCPF3213 procedure, so if we want to remove them from the job log, we can simply call the QMHRMVPM API a second time as shown here.
pHdlCPF3213 b
/free
Cmd = 'CHGPF FILE(SOMEFILE) MAXMBRS(*NOMAX)';
RunCmd(Cmd :%len(%trimr(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :QUSEC);
Cmd = 'ADDPFM FILE(SOMEFILE) MBR(' + PSDS.JobUsr + ')';
RunCmd(Cmd :%len(%trimr(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :ErrCde);
if ErrCde.Common.QUSBAVL > 0;
SndHardError();
endif;
RmvMsg('*' :0 :MessageInfo.Common.QMHMK03 :'*BYKEY' :QUSEC);
RmvMsg('*' :0 :' ' :'*ALL' :QUSEC);
/end-free
pHdlCPF3213 e
With these two API calls, HdlCPF3213 is removing all messages related to the successful handling of CPF7306, leaving the job log with only those messages related to true failures within the application program. This is the approach to removing messages from the job log that I generally recommend and use in my own applications.
Special Value *ALLINACT
Before ending this article, I would like to point out one other special value that the QMHRMVPM API supports—a special value that can be quite helpful but also a bit on the dangerous side. The special value is *ALLINACT for the Call stack entry parameter. *ALLINACT, which requires that you also specify the special value *ALL for the Messages to remove parameter, allows you to remove all messages associated with inactive call stack entries—that is, programs that have returned and not "cleaned up" after themselves. Using this special value, we could for instance have the main procedure of our sample program remove the messages generated by the HdlCPF3213 procedure after the procedure returned (which we might do for example if HdlCPF3213 was provided in a separate module, we didn't have the source code for the module, and we really didn't want the CPC messages in our job log). This special value can be quite useful when calling system functions and/or user applications that leave unwanted messages in the job log.
There is, however, an implication with *ALLINACT that catches many by surprise. Let's say you have a program A that calls program B. Program B then calls program C, which generates a few messages in your job log. When program C returns to program B, B can then call the QMHRMVPM API with *ALLINACT and remove all of the messages in program C's program message queue as C is no longer an active call stack entry within your job. Sounds perfect so far, right? Now let's have program A call program X, where X generates a few messages and returns to A. As before, A then calls B, which in turn calls C. When C returns to B, and B uses the *ALLINACT special value of the QMHRMVPM API, the messages associated with both programs X and C will be removed as neither have an active call stack entry. The "surprise" is related to many developers thinking of call stacks in terms of what is directly "above" and "below" them in the call stack. In this case, program X was never "below" (or called) by B, but from a job point of view, X meets all of the requirements of being inactive. When using *ALLINACT, keep in mind that it will remove all messages in the job log that resulted from any commands or programs that have been previously run and that are no longer active in the call stack. Due to this, *ALLINACT may get rid of a lot more messages than you expect.
Unwanted Messages Gone!
In this article, we've looked at several ways that you can remove unwanted messages from your job logs. There are several additional options and features with the QMHRMVPM API that we haven't discussed, but the approaches reviewed here should enable you to more directly control what is, and perhaps more importantly what is not, in your job logs when chasing down a problem.
Questions?
If you have any API questions, send them to me at
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,
LATEST COMMENTS
MC Press Online