Let the User Log be your guide when you track an application through your system.
Brief: Job logs contain too much unwanted detail to serve as a trail of events, and they can only report on one job. The User Log presented here avoids these limitations.
The AS/400 job log has many uses. In its printed form, it is an invaluable tool in problem determination. The first question that I (and probably you) ask an operator when a program abends is, "Do you have the job log?" This log, along with the program dump, will usually provide you with the answers you need, or at least point you in the right direction in order to solve the problem.
I don't know if you've ever tried using the job log as a program audit trail, however. If you have, you've probably given up after seeing how sketchy the job log can be in some cases, or how full of unnecessary information, in others. The truth of the matter is that in most cases you can't use the job log this way.
For one thing, the job log can't report anything that happens in another job, even if it has direct impact on the operations you're performing in your job. For example, you can't follow the trail of a batch job to the interactive job that submitted it, or get a complete picture of a system that runs in multiple interactive jobs, whether they run parallel or not.
The job log's shortcomings as an audit trail became apparent as I faced the task of developing an automated system for our nightly operations. I wanted a complete picture of everything that happens with this system and there was no way I could juggle job logs to produce the results I needed. So, I wrote the User Log utility to solve the problems that a job log simply couldn't handle.
The User Log
The basic concept of the User Log is simple. It's a command that writes text into a physical file whenever you use it in your programs. The immediate advantage to this method is that you can send text from several different programs to the same physical file, accumulate it for as long as you wish (even across IPLs) and print it at your convenience. The programs don't all have to be in the same program stack, don't have to run at the same time, and can be run an unlimited number of times (accumulating text in the same physical file each time). The converse of this is also true. One program can write to an unlimited number of log files. This allows you the benefit of tracking your operations in any manner you see fit. Now try to do that with job logs!
This utility consists of two commands: Write to User Log (WRTUSRLOG), 2a, and Print User Log (PRTUSRLOG), 3a. The Command Processing Programs (CPPs) for the WRTUSRLOG command are USR002CL and USR002RG,2b and 2c. The CPPs for the PRTUSRLOG command are USR003CL and USR003RG,3b and 3d. In addition, the utility uses physical file USRLOG, 1, and printer file USR003P1, 3c. There is also a message file, USR002MF, 4, which you will need to create (along with four message descriptions).
This utility consists of two commands: Write to User Log (WRTUSRLOG), Figure 2a, and Print User Log (PRTUSRLOG), Figure 3a. The Command Processing Programs (CPPs) for the WRTUSRLOG command are USR002CL and USR002RG, Figures 2b and 2c. The CPPs for the PRTUSRLOG command are USR003CL and USR003RG, Figures 3b and 3d. In addition, the utility uses physical file USRLOG, Figure 1, and printer file USR003P1, Figure 3c. There is also a message file, USR002MF, Figure 4, which you will need to create (along with four message descriptions).
Writing to the User Log
You may log two types of text: plain text using the MSG parameter, or predefined text with message data using the MSGID parameter. Using the plain text approach, your WRTUSRLOG command will look something like this:
WRTUSRLOG MSG('This is a plain + text message') + MSGTYPE(*INFO) + LOGFILE(MYLIB/MYLOGFILE)
This sends your text to MYLIB/MYLOGFILE as is. It remains there until you decide to clear it out.
The predefined message approach requires a message ID and a message file. This method has greater flexibility and creates a modular approach to your messages. Using predefined messages, the WRTUSRLOG command will look something like this:
WRTUSRLOG MSGID(CPF0901) + MSGTYPE(*NOTIFY) + MSGF(QSYS/QCPFMSG) + MSGDTA('QSECOFR') + LOGFILE(MYLIB/MYLOGFILE)
This inserts the message data value 'QSECOFR' into the CPF0901 message description "PWRDWNSYS command issued by user &1 and is being processed" and sends the resulting text to MYLIB/MYLOGFILE. You may predefine your messages in your own message file or in USR002MF, which is included in this utility.
The MSGTYPE parameter in the WRTUSRLOG command accepts any valid message type and is provided for your reference on the printed report produced from the PRTUSRLOG command. Three special message types are acceptable to the WRTUSRLOG command: *BEGIN, *STEP and *END. These message types do nothing different in the logging function, but they do provide a particular selection in the PRTUSRLOG command. You can use *BEGIN to log the beginning of a job or task, and *END to log its end. In between, use *STEP each time your program reaches an important milestone.
The LOGFILE parameter consists of the qualified file name of the file to receive your message. If the file exists, your message is added; if the file does not exist, it is created for you by the USR002CL program.
Printing the User Log
The PRTUSRLOG command is designed to be as versatile as possible. You may select any previously created log file to be printed. The file may optionally be cleared or deleted after printing. You can print a selected message type from the file, or you may print only messages from a certain user, job or number. It is not necessary to fully qualify the JOB parameter in the PRTUSRLOG command. In other words, you may select to print messages from QSECOFR regardless of the job name and number. Or, you may select to print messages from job name MYJOB regardless of which user ran the job. Presented here are a few examples of the proper syntax of the PRTUSRLOG command:
PRTUSRLOG LOGFILE(MYLIB/LOGFILE)
This is the simplest form of the PRTUSRLOG command. It prints the entire contents of your log file.
PRTUSRLOG LOGFILE(MYLIB/LOGFILE) + MSGTYPE(*ERROR) + OPTION(*CLEAR)
This prints all messages of type *ERROR from your log file and clears the log file of all entries afterward. You may want to modify the clear function to clear only the message types that were selected for printing.
PRTUSRLOG LOGFILE(MYLIB/LOGFILE) + MSGTYPE(*FLOW) + OPTION(*DELETE)
Specifying MSGTYPE(*FLOW) on the PRTUSRLOG command selects only message types *BEGIN, *STEP and *END. In this example, the log file will be deleted after the messages are printed. You may not specify OPTION
(*DELETE) for the base log file USRLOG. This file is used as a blueprint to dynamically create other log files, and without it the utility will not function properly.
PRTUSRLOG LOGFILE(MYLIB/LOGFILE) + JOB(MYJOB)
Only messages which originated from MYJOB are printed; it does not limit the report to one particular occurrence or user of MYJOB.
PRTUSRLOG LOGFILE(MYLIB/LOGFILE) + JOB(MIKEB/MYJOB)
Again, only messages which originated from MYJOB are printed; but only those messages that were sent by user MIKEB are included.
PRTUSRLOG LOGFILE(MYLIB/LOGFILE) + JOB(MIKEB/*ALL)
Messages sent by MIKEB to this log are printed regardless of the job they were sent from.
PRTUSRLOG LOGFILE(MYLIB/LOGFILE) + MSGTYPE(*FLOW) + JOB(123456/MIKEB/*ALL)
The above command prints only *BEGIN, *STEP and *END messages from one specific job.
So there it is. I like it (a biased opinion, of course) and I sincerely hope that you find as many uses for this utility as I have. I wouldn't trade the job log for anything in a problem determination situation; but if establishing a program and security audit trail is important to you, I believe that it is well worth the effort to sprinkle your source code liberally with the WRTUSRLOG command.
Better Than Job Logs...
Figure 1 Physical file USRLOG
A R ULREC A ULDATE 6P 0 TEXT('ENTRY DATE') A EDTCDE(Y) A ULTIME 6P 0 TEXT('ENTRY TIME') A EDTWRD('0 : : ') A ULMSGI 7A TEXT('MESSAGE ID') A ULMSGL 10A TEXT('MESSAGE FILE LIBRARY') A ULMSGF 10A TEXT('MESSAGE FILE') A ULMSGT 7A TEXT('MESSAGE TYPE') A ULJOB# 6A TEXT('JOB NUMBER') A ULJOBN 10A TEXT('JOB NAME') A ULJOBU 10A TEXT('JOB USER PROFILE') A ULMSGX 180A TEXT('MESSAGE TEXT')
Better Than Job Logs...
Figure 2A Command WRTUSRLOG
WRTUSRLOG: CMD PROMPT('Write User Log') PARM KWD(LOGFILE) TYPE(QUAL1) MIN(1) + PROMPT('Write to log file') PARM KWD(MSG) TYPE(*CHAR) LEN(180) EXPR(*YES) + PROMPT('Message text') PARM KWD(MSGID) TYPE(*SNAME) LEN(7) EXPR(*YES) + PROMPT('Message ID') PARM KWD(MSGTYPE) TYPE(*CHAR) LEN(7) RSTD(*YES) + DFT(*ERROR) VALUES(*BEGIN *END *STEP + *ERROR *INFO *INQ *RQS *COMP *DIAG + *NOTIFY *ESCAPE *STATUS) EXPR(*YES) + PROMPT('Message type') PARM KWD(MSGF) TYPE(QUAL2) PMTCTL(MSGID) + PROMPT('Message file') PARM KWD(MSGDTA) TYPE(*CHAR) LEN(90) EXPR(*YES) + PMTCTL(MSGID) PROMPT('Message data') QUAL1: QUAL TYPE(*NAME) LEN(10) MIN(1) EXPR(*YES) QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) + SPCVAL((*LIBL)) EXPR(*YES) PROMPT('Library') QUAL2: QUAL TYPE(*NAME) LEN(10) DFT(QCPFMSG) EXPR(*YES) QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) + SPCVAL((*LIBL)) EXPR(*YES) PROMPT('Library') MSGID: PMTCTL CTL(MSG) COND((*EQ ' ')) DEP CTL(&MSG *EQ ' ') PARM((&MSGID *GT ' ')) + MSGID(LOG0001) DEP CTL(&MSGID *EQ ' ') PARM((&MSG *GT ' ')) + MSGID(LOG0001) DEP CTL(&MSG *GT ' ') PARM((&MSGID *EQ ' ')) + MSGID(LOG0002) DEP CTL(&MSGID *GT ' ') PARM((&MSG *EQ ' ')) + MSGID(LOG0002) DEP CTL(&MSGDTA *GT ' ') PARM((&MSG *EQ ' ')) + MSGID(LOG0003)
Better Than Job Logs...
Figure 2B CL program USR002CL
USR002CL: + PGM PARM(&QLOGFIL &MSG &MSGID &MSGTYPE &QMSGF &MSGDTA) DCL VAR(&JOB) TYPE(*CHAR) LEN(10) DCL VAR(&LOGFIL) TYPE(*CHAR) LEN(10) DCL VAR(&LOGLIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSG) TYPE(*CHAR) LEN(180) DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(90) DCL VAR(&MSGF) TYPE(*CHAR) LEN(10) DCL VAR(&MSGFLIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSGID) TYPE(*CHAR) LEN(7) DCL VAR(&MSGTYPE) TYPE(*CHAR) LEN(7) DCL VAR(&NBR) TYPE(*CHAR) LEN(6) DCL VAR(&QLOGFIL) TYPE(*CHAR) LEN(20) DCL VAR(&QMSGF) TYPE(*CHAR) LEN(20) DCL VAR(&RTNLIB) TYPE(*CHAR) LEN(10) DCL VAR(&USER) TYPE(*CHAR) LEN(10) MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(SNDERRMSG)) /* Break qualified names */ CHGVAR VAR(&MSGF) VALUE(%SST(&QMSGF 1 10)) CHGVAR VAR(&MSGFLIB) VALUE(%SST(&QMSGF 11 10)) CHGVAR VAR(&LOGFIL) VALUE(%SST(&QLOGFIL 1 10)) CHGVAR VAR(&LOGLIB) VALUE(%SST(&QLOGFIL 11 10)) /* Retrieve message from message file if necessary */ IF COND(&MSGID *NE ' ') THEN(DO) RTVMSG MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) MSGDTA(&MSGDTA) + MSG(&MSG) ENDDO ELSE CMD(DO) CHGVAR VAR(&MSGF) VALUE(' ') CHGVAR VAR(&MSGFLIB) VALUE(' ') ENDDO RTVJOBA JOB(&JOB) USER(&USER) NBR(&NBR) /* Create LOGFILE if nonexistent */ RTVOBJD OBJ(*LIBL/USR002CL) OBJTYPE(*PGM) RTNLIB(&RTNLIB) CRTDUPOBJ OBJ(USRLOG) FROMLIB(&RTNLIB) OBJTYPE(*FILE) + TOLIB(&LOGLIB) NEWOBJ(&LOGFIL) DATA(*NO) MONMSG MSGID(CPF0000) /* Write the message out to the specified file */ OVRDBF FILE(USRLOG) TOFILE(&LOGLIB/&LOGFIL) CALL PGM(USR002RG) PARM(&MSGID &MSGFLIB &MSGF &MSGTYPE &MSG + &JOB &USER &NBR) RETURN /* Send error messages */ SNDERRMSG: + RCVMSG MSGTYPE(*EXCP) MSGDTA(&MSGDTA) MSGID(&MSGID) MSGF(&MSGF) + MSGFLIB(&MSGFLIB) SNDPGMMSG MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) MSGDTA(&MSGDTA) + MSGTYPE(*ESCAPE) ENDPGM
Better Than Job Logs...
Figure 2C RPG program USR002RG
FUSRLOG O E DISK A * C *ENTRY PLIST C PARM ULMSGI C PARM ULMSGL C PARM ULMSGF C PARM ULMSGT C PARM ULMSGX C PARM ULJOBN C PARM ULJOBU C PARM ULJOB# * C TIME TIMDAY 120 C MOVELTIMDAY ULTIME 60 C MOVE TIMDAY ULDATE 60 * C WRITEULREC C SETON LR
Better Than Job Logs...
Figure 3A Command PRTUSRLOG
PRTUSRLOG: CMD PROMPT('Print User Log') PARM KWD(LOGFILE) TYPE(QUAL1) MIN(1) PROMPT('Log + file to be printed') PARM KWD(MSGTYPE) TYPE(*CHAR) LEN(7) RSTD(*YES) + DFT(*ALL) VALUES(*ALL *BEGIN *END *STEP + *FLOW *ERROR *INFO *INQ *RQS *COMP *DIAG + *NOTIFY *ESCAPE *STATUS) EXPR(*YES) + PROMPT('Message type') PARM KWD(OPTION) TYPE(*CHAR) LEN(7) RSTD(*YES) + DFT(*NONE) VALUES(*NONE *DELETE *CLEAR) + EXPR(*YES) PROMPT('End of report option') PARM KWD(JOB) TYPE(QUAL2) PMTCTL(*PMTRQS) + PROMPT('Select job name') QUAL1: QUAL TYPE(*NAME) LEN(10) MIN(1) QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) + SPCVAL((*LIBL)) PROMPT('Library') QUAL2: QUAL TYPE(*NAME) LEN(10) DFT(*ALL) SPCVAL((*ALL)) QUAL TYPE(*NAME) LEN(10) PROMPT('User') QUAL TYPE(*CHAR) LEN(6) RANGE(000000 999999) + FULL(*YES) EXPR(*YES) PROMPT('Number') DEP CTL(&OPTION *EQ *DELETE) PARM((&LOGFILE *NE + USRLOG)) MSGID(LOG0004)
Better Than Job Logs...
Figure 3B CL program USR003CL
USR003CL: + PGM PARM(&QLOGFIL &MSGTYPE &OPTION &QJOB) DCL VAR(&OPTION) TYPE(*CHAR) LEN(7) DCL VAR(&JOB) TYPE(*CHAR) LEN(10) DCL VAR(&LOGFIL) TYPE(*CHAR) LEN(10) DCL VAR(&LOGLIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(80) DCL VAR(&MSGF) TYPE(*CHAR) LEN(10) DCL VAR(&MSGFLIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSGTYPE) TYPE(*CHAR) LEN(7) DCL VAR(&MSGID) TYPE(*CHAR) LEN(7) DCL VAR(&NBR) TYPE(*CHAR) LEN(6) DCL VAR(&PRTDEV) TYPE(*CHAR) LEN(10) DCL VAR(&QJOB) TYPE(*CHAR) LEN(26) DCL VAR(&QLOGFIL) TYPE(*CHAR) LEN(20) DCL VAR(&USER) TYPE(*CHAR) LEN(10) MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(SNDERRMSG)) /* Break qualified names */ CHGVAR VAR(&LOGFIL) VALUE(%SST(&QLOGFIL 1 10)) CHGVAR VAR(&LOGLIB) VALUE(%SST(&QLOGFIL 11 10)) CHGVAR VAR(&JOB) VALUE(%SST(&QJOB 1 10)) CHGVAR VAR(&USER) VALUE(%SST(&QJOB 11 10)) CHGVAR VAR(&NBR) VALUE(%SST(&QJOB 21 6)) /* Check existence of user log file */ CHKOBJ OBJ(&LOGLIB/&LOGFIL) OBJTYPE(*FILE) /* Print user log */ OVRDBF FILE(USRLOG) TOFILE(&LOGLIB/&LOGFIL) CALL PGM(USR003RG) PARM(&LOGLIB &LOGFIL &MSGTYPE &JOB &USER &NBR) DLTOVR FILE(*ALL) /* Clear or delete user log file, if requested */ IF COND(&OPTION *EQ '*CLEAR') THEN(DO) CLRPFM FILE(&LOGLIB/&LOGFIL) ENDDO ELSE CMD(IF COND(&OPTION *EQ '*DELETE') THEN(DO)) DLTF FILE(&LOGLIB/&LOGFIL) ENDDO RETURN /* Send error messages */ SNDERRMSG: + RCVMSG MSGTYPE(*EXCP) MSGDTA(&MSGDTA) MSGID(&MSGID) MSGF(&MSGF) + MSGFLIB(&MSGFLIB) SNDPGMMSG MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) MSGDTA(&MSGDTA) + MSGTYPE(*ESCAPE) ENDPGM
Better Than Job Logs...
Figure 3C Printer file USR003P1
A REF(USRLOG) * A R HEADER SKIPB(3) A 1'Date:' A +2DATE EDTCDE(Y) A 42'Print User Log Report' A 119'Page:' A +2PAGNBR EDTCDE(J) A SPACEA(1) A 33'Logfile is' A LOGFIL 10 +1 A 55'in library' A LOGLIB 10 +1 A 119'Time:' A +1TIME A SPACEA(1) A 1'-------------------------' A 26'-------------------------' A 51'-------------------------' A 76'-------------------------' A 101'-------------------------' A 126'-------' A SPACEA(1) A 1'Date, Time & Type' A 23'Message Text' A 114'Sender/Msg ID' A SPACEA(1) A 1'-------------------------' A 26'-------------------------' A 51'-------------------------' A 76'-------------------------' A 101'-------------------------' A 126'-------' A SPACEA(2) * A R DETAIL A ULDATE R 1 A ULMSGT R 11 A 20'|' A MSG1 90 22 A 113'|' A ULJOBN R 114 A ULJOB# R 127 A SPACEA(1) A ULTIME R 2 A 20'|' A MSG2 90 22 A 113'|' A ULJOBU R 114 A ULMSGI R 126 A SPACEA(1) A 20'|' A 113'|' A SPACEA(1)
Better Than Job Logs...
Figure 3D RPG program USR003RG
FUSRLOG IP E DISK FUSR003P1O E 99 PRINTER * C MSGTYP IFNE '*ALL' C MSGTYP ANDNE'*FLOW' C ULMSGT ANDNEMSGTYP * C MSGTYP OREQ '*FLOW' C ULMSGT ANDNE'*BEGIN' C ULMSGT ANDNE'*STEP' C ULMSGT ANDNE'*END' * C JOBNAM ORNE '*ALL' C ULJOBN ANDNEJOBNAM * C JOBUSR ORNE *BLANK C ULJOBU ANDNEJOBUSR * C JOBNBR ORNE *BLANK C ULJOB# ANDNEJOBNBR * C ELSE C *IN99 IFEQ *ON C WRITEHEADER C MOVE *OFF *IN99 C ENDIF C 90 SUBSTULMSGX:1 MSG1 C 90 SUBSTULMSGX:91 MSG2 C ULMSGT IFEQ '*STEP' C MOVE *BLANK ULMSGT C MOVE '*STEP' ULMSGT C ENDIF C WRITEDETAIL C ENDIF * C *INZSR BEGSR C *ENTRY PLIST C PARM LOGLIB C PARM LOGFIL C PARM MSGTYP 7 C PARM JOBNAM 10 C PARM JOBUSR 10 C PARM JOBNBR 6 C WRITEHEADER C ENDSR
Better Than Job Logs...
Figure 4 Creation of message file USR002MF
Figure 4: Creation of Message File USR002MF CRTMSGF MSGF(xxx/USR002MF) + TEXT('Message file for WRTUSRLOG/PRTUSRLOG') ADDMSGD MSGID(USR0001) MSGF(xxx/USR002MF) + MSG('You must specify either MSG or MSGID.') ADDMSGD MSGID(USR0002) MSGF(xxx/USR002MF) + MSG('You may not specify both MSG and MSGID.') ADDMSGD MSGID(USR0003) MSGF(xxx/USR002MF) + MSG('MSGDTA is invalid for this type of message.') ADDMSGD MSGID(USR0004) MSGF(xxx/USR002MF) + MSG('OPTION(*DELETE) is invalid with LOGFILE(USRLOG).')
LATEST COMMENTS
MC Press Online