Another addition to the RPG programming language with IBM i 7.5 and 7.4 TR6 is an operation code that allows me to write to the current job's job log.
I have been using the SQL procedure LPRINTF to write to the job log since 2019. Now I have the ability to do so with native RPG.
The new operation code is called Send Message, SND-MSG, and is accompanied by two new optional Built in Functions, BiF: %MSG and %TARGET.
Just how simple it is to write to job log I will demonstrate below. Let me start with my first example program's source code:
01 **free 02 dcl-s Text varchar(50) inz('Second message') ; 03 snd-msg 'First message' ; 04 snd-msg Text ; 05 snd-msg *INFO 'Third message' ; |
Line 1: If I am not writing my RPG in totally free format then I am making it too complicated.
Line 2: I am defining a variable to contain the text I will be using for a message. Notice that I have define the variable as VARCHAR.
Line 3: This is the simplest form of this operation code. Here I am just writing an informational message to the job log where the message's text is a string.
Line 4: Here I writing another informational message using the text from the variable I defined.
Line 5: I could use the keyword *INFO to denote that this is an informational message. But this is SND-MSG's default message type. I have just put *INFO in upper case here so you would notice it. It can be entered in either case, or even mixed.
I am going to use the SQL JOBLOG_INFO table function to retrieve the messages from the job log. My SQL Select statement would be:
SELECT MESSAGE_TEXT,MESSAGE_TYPE,MESSAGE_ID FROM TABLE(QSYS2.JOBLOG_INFO('310968/SIMON/QPAD143458')) |
I am only interested in three columns from JOBOG_INFO:
- MESSAGE_TEXT: Text of the message
- MESSAGE_TYPE: Message type. INFORMATION, ESCAPE, etc.
- MESSAGE_ID: Message id for the generated message
The results for the above RPG snippet is:
MESSAGE_TEXT MESSAGE_TYPE MESSAGE_ID --------------- ------------- ---------- First message INFORMATIONAL <NULL> Second message INFORMATIONAL <NULL> Third message INFORMATIONAL <NULL> |
None of these messages have a message id as I generated them "on the fly".
I can use the %MSG BiF to send that message to the job log. Before I can show that I am going to need to have a message file, and a message within it I can use. I generated by own using the following CL commands:
01 CRTMSGF MSGF(MYLIB/MYMSGF) 02 ADDMSGD MSGID(MY00001) MSGF(MYMSGF) MSG('This is a message from my message file') SECLVL('This is a message a created for SND-MSG in my message file') |
Line 1: I created a brand-new message file in my library.
Line 2: I added a message to that message file.
I can check my message one of two ways. The first uses the Display Message Description command, DSPMSGD:
DSPMSGD RANGE(MY00001) MSGF(MYMSGF) |
Or I can use the SQL View MESSAGE_FILE_DATA. Here I would use:
SELECT MESSAGE_ID,MESSAGE_TEXT,SEVERITY FROM QSYS2.MESSAGE_FILE_DATA WHERE MESSAGE_FILE_LIBRARY = 'MYLIB' AND MESSAGE_FILE = 'MYMSGF' AND MESSAGE_ID = 'MY00001' ; |
Which returns to me:
MESSAGE_ID MESSAGE_TEXT SEVERITY ---------- -------------------------------------- -------- MY00001 This is a message from my message file 0 |
Back to my RPG source code, and the next three lines are:
06 snd-msg %MSG('MY00001' : 'MYMSGF') ; 07 snd-msg *INFO %MSG('MY00001' : 'MYMSGF') ; 08 snd-msg %MSG('CPF9898' : 'QCPFMSG' : 'Replacement text') ; |
Line 6: Writes the message I created in my message file to the job log as an informational message. Here I am only using the first two parameters of the BiF:
- Message id
- Message file name
Line 7: I could have inserted the *INFO to do the same, to be honest what is the point as this is the default?
Line 8: The message CPF9898 allows the use replacement text. The replacement text I want to write to the job log is the third parameter of the %MSG BiF.
In these three source lines I have used %MSG in upper case, I did so to allow draw it to your attention. I can be entered in lower or mixed case too.
When I use the SQL Select statement with the JOBLOG_INFO table function I receive the following results:
MESSAGE_TEXT MESSAGE_TYPE MESSAGE_ID -------------------------------------- ------------- ---------- This is a message from my message file INFORMATIONAL MY00001 This is a message from my message file INFORMATIONAL MY00001 Replacement text. INFORMATIONAL CPF9898 |
The other BiF I can use with SND-MSG is %TARGET. It must always be the last BiF used, following %MSG or the message text.
In its simplest form I can use %TARGET thus:
09 snd-msg 'Fourth message' %TARGET(*SELF) ; |
*SELF within the Target BIF sends the message to the procedure this is included within. %TARGET can contain other values when used to generate an Escape message. Here is my second example program's source code:
01 **free 02 ctl-opt dftactgrp(*no) ; 03 SubProc1() ; 04 *inlr = *on ; //---------------------- 05 dcl-proc SubProc1 ; 06 SubProc2() ; 07 end-proc ; //---------------------- 08 dcl-proc SubProc2 ; 09 SubProc3() ; 10 end-proc ; //---------------------- 11 dcl-proc SubProc3 ; 12 snd-msg *ESCAPE 'Escape message' %TARGET(*CALLER : 0) ; 13 end-proc ; |
The program contains three subprocedures, SubProc1, SubProc2, and SubProc3.
Lines 1 – 4: The main line of this RPG program just calls the first subprocedure.
Lines 5 – 7: The first subprocedure just calls the second procedure.
Lines 8 – 10: In turn the second subprocedure calls the third.
Lines 11 – 13: In the third subprocedure I send an Escape type message. Notice in the %TARGET I have *CALLER followed by zero. This means that the message is returned the previous step in the call stack, in this case the second procedure.
When I call this program, and using the JOBLOG_INFO table function, my results are:
MESSAGE_TEXT ----------------------------------------------------------------------------- Escape message. The call to SUBPROC1 ended in error (C G D F). C The call to SUBPROC1 ended in error (C G D F). C Application error. CPF9898 unmonitored by TESTPGM at statement 0000000900... Application error. CPF9898 unmonitored by TESTPGM at statement 0000000900... MESSAGE_TYPE MESSAGE_ID ------------- ---------- ESCAPE CPF9898 SENDER RNQ0202 REPLY <NULL> INQUIRY RNQ0202 REPLY <NULL> ESCAPE CEE9901 INFORMATIONAL CEE9901 |
It is important to notice the line number shown in the last two messages. This is the line number of where the third procedure was called, line 9.
I change the second parameter in the target to "1".
12 snd-msg *ESCAPE 'Escape message' %TARGET(*CALLER : 1) ; |
When I call the program again the last two message lines are:
MESSAGE_TEXT ----------------------------------------------------------------------------- Application error. CPF9898 unmonitored by TESTPGM at statement 0000000600... Application error. CPF9898 unmonitored by TESTPGM at statement 0000000600... |
Notice that the statement number is now 6. By incrementing the second parameter I have gone up one more position in the call stack to where the second procedure is called.
If I increase the second parameter again:
12 snd-msg *ESCAPE 'Escape message' %TARGET(*CALLER : 2) ; |
And I run the program again I receiving the following last two message lines:
MESSAGE_TEXT ----------------------------------------------------------------------------- Application error. CPF9898 unmonitored by TESTPGM at statement 0000000300... Application error. CPF9898 unmonitored by TESTPGM at statement 0000000300... |
I have "climbed up" one more step in the call stack and this is now referencing the call to the first subprocedure in the main line part of the program.
If I wanted to keep the message with the subprocedure it occurred within I would use the target with *SELF:
12 snd-msg *ESCAPE 'Escape message' %TARGET(*SELF) ; |
My results show the line number the message was issued by SND-MSG.
MESSAGE_TEXT ----------------------------------------------------------------------------- Application error. CPF9898 unmonitored by TESTPGM at statement 0000001200... Application error. CPF9898 unmonitored by TESTPGM at statement 0000001200... |
IMHO this is a useful addition to the RPG programming language. I can certainly see myself using SND-MSG to write informational messages to the job log, rather than using LPRINTF.
You can learn more about this from the IBM website:
This article was written for IBM i 7.5 and 7.4 TR6.
Would be nice if snd-msg could write to a table so could be queried later. Messages in job logs tend to get lost or deleted over time.
ReplyDeleteRinger
Seems about 30 years late. This is literally one of the first things I tried to do when I started programming on production machines and immediately went with the qp0zLprintf api.
ReplyDeleteWhy they restricted the msg type to *INFO and *ESCAPE is another big miracle.
ReplyDeleteI like to send *DIAG and *COMP messages, too.
thanks Simon, snd-msg opcode just getting better
ReplyDeleteDoes anyone know if this process uses the SNDDST command?
ReplyDelete