Wednesday, June 8, 2022

New RPG op-code to send messages to job log

rpg snd-msg %msg %target

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:

  1. MESSAGE_TEXT:  Text of the message
  2. MESSAGE_TYPE:  Message type. INFORMATION, ESCAPE, etc.
  3. 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:

  1. Message id
  2. 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.

4 comments:

  1. 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.

    Ringer

    ReplyDelete
  2. 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.

    ReplyDelete
  3. Why they restricted the msg type to *INFO and *ESCAPE is another big miracle.
    I like to send *DIAG and *COMP messages, too.

    ReplyDelete
  4. thanks Simon, snd-msg opcode just getting better

    ReplyDelete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.