Published on System iNetwork (http://systeminetwork.com)
Save Job Logs to a Database File -- The Easy Way
By cbushong
Created Apr 28 2008 - 18:46

By:
Bob Cozzi [1]

Save Your Job Logs

To a Database File

If you've ever used DSPJOBLOG OUTPUT(*PRINT) to record someone's job log, you know how much fun it can be processing spool files in RPG. Most people either save the spool file or override the output to a database file and then process it as necessary. That's where the Control Job Log Output (QMHCTLJL) API enters the picture.

The QMHCTLJL API redirects a job's job log to a user-specified database file. Sadly, it doesn't have an option to write immediately to the file; instead, it waits for the user to sign off or the batch job to end and then performs the normal job log generation.

What I mean by normal job log generation is that if the user signs off with *NOLIST, then no job log is produced and nothing is recorded by QMHCTLJL, but if the user signs off with *LIST, then the job log is redirected by this API. In other words, the API only redirects where the output is sent, not whether it is generated nor when its generated.

But this is good enough for our purposes, so I'm going to explain the API in a little more detail and then give you a working program that you can call to redirect "this" job's job log to a database file of your choosing.

First, you call this API at any point in the job's life cycle to perform a redirection of where the job log is written. It can be a database file (two database files actually) or *PRINT. Why *PRINT? In case you redirect a job log to a database file and then want to redirect it back to the standard SPOOL file output mechanism. So, you call QMHCTLJL once (or more) and wait for the job to end to see the results. But again, if the job is an interactive job, and the end user types in SIGNOFF *NOLIST (which is the default in most shops), you'll get nothing!

The QMHCTLJL API has six required parameters, which are

  • Output files
  • Output file structure format name (always CTLJ0100).
  • Message filtering array
  • Message filtering array element count
  • Message queue name to report errors
  • Traditional API exception/error data structure

The first five parameters are input only, and the error structure (parameter six) Is, of course, input/output.

The first parameter is a data structure that contains two file and member names, along with a replace-data option.

The two files and members are referred to as the primary output file and the secondary output file.

The primary output file is required and receives the basic message-related information, much of which you'd retrieve using the RCVMSG CL command -- things like severity, time sent, message ID, msgkey, type, sender data, message file, message data, and so on. In fact, you could reproduce the actually message itself with just this information. The file's record length is (as of V5.1) 18,263 bytes wide. And as far as I can tell, there is no published record format for this file. So using the external descriptions or generating it and using my RTVSQLSRC CL command are the only known ways to build a file description.

The secondary output file is optional; you simply leave this entry blank and nothing is produced. If specified, the secondary file receives the first- and second-level message text, the message type (*RQS, *COMP, *INFO, etc.), and some miscellaneous information. There is a subtle way to tie these two files together, and that requires joining the files using the message key. The msgkey is the only consistent, unique value stored in both files.

Just for fun, I created a simple SQL join statement to bring the important data from both files into a single view of the information. That SQL statement follows:

SELECT P.QMHMID, P.QMHTYP, P.QMHSEV, P.QMHMDT, S.QMHLIN 
       FROM rpgcoder/joblog1 P, rpgcoder/joblog2 S 
       WHERE P.QMHMRK=S.QMHMKS

The second parameter is the output file format ID. This parameter should always contain the literal 'CTLJ0100'. These format IDs exist so that if IBM enhances an API later on, rather than create a new API, IBM can simply add a new format ID and be done with it.

The third and fourth parameters are the message filter array and array element count. This is an array of filter identifiers. For example, if you only want completion messages and informational messages recorded, you would pass two message filter elements -- one with *COMP and one with *INFO specified. Then pass two for the element count (fourth parameter). You also get to specify a message ID and message severity level for these filters.

The fifth parameter is a qualified message queue that receives messages if/when the API fails while attempting to produce the job log. Normally I leave this parameter blank but, of course, your situation may call for it.

The final parameter is that traditional API exception/error data structure.

Using the QMHCTLJL API

This API can be called at any time during a job to change where its job log is written. You can't run this API "for another job" but only for "this" job. That is, the job in which the API is called is alternated.

To simplify things, I've written a Save Job Log (SAVJOBLOG) Command. This command allows you to use CL to run the QMHCTLJL API much more easily. The command lets you control everything except the error message queue parameter, which I tend not to use anyway. But adding it should be a simple process.

The SAVJOBLOG Command

The SAVJOBLOG command that I wrote has six parameters, but they do not correspond to the six parameters of the API, rather they correspond to entering the information to call the API.

OUTFILE - The OUTFILE parameter is a qualified database file that the API refers to as the primary file. This file receives the detail message information from the job log. If this file does not exist, it is created.

A special value of OUTFILE(*PRINT) may be specified for this parameter. This routes the job log to the normal spool file system. The other OUTFILE parameters are ignored when OUTFILE(*PRINT) is specified.

OUTMBR - The OUTMBR parameter is the data member of the OUTFILE that receives the job log information. If the member does not exist, it is added to the OUTFILE.

OUTFILE2 - The OUTFILE2 parameter is the optional database file that the API refers to as the secondary file. This file receives the summary message information from the job log. If the file does not exist, it is created. A value of *NONE for this parameter indicates that job log information is not written to the secondary file.

OUTMBR2 - The OUTMBR2 parameter is the data member of the OUTFILE2 that receives the job log information. If the member does not exist, it is added to the OUTFILE2.

REPLACE - The REPLACE parameter is used to control overwriting of data in the primary and secondary files. A value of REPLACE(*YES) replaces any existing data.

MSGFILTER - The MSGFILTER parameter allows you to control what is written to the primary and secondary files. You can specify up to 20 combinations of message type, message severity, and message ID (generic or full). If 20 isn't enough, just change the Command definition from MAX(20) to a larger value, and change the MAXFILTERS named constant to the same value in the RPG IV source code.

Here's an example of using SAVJOBLOG on the Command Entry display:

==> SAVJOBLOG OUTFILE(QGPL/MYJOBLOG1) OUTFILE2(QGPL/MYJOBLOG2) +
              MSGFILTER((*ESCAPE) (*EXCP)) 

In this example, two files are used to log the job log information, but only escape and exception messages are written to the files. Note that the writing only occurs after the interactive job ends (i.e., after signoff) and only if LOG(*LIST) is specified. Otherwise, you get nothing.

If you've never worked with a user-written command before, enter the CMD source code into a source file named QCMDSRC and set the SEU source type to CMD. To compile, just use PDM option 14; the default CRTCMD parameters are fine.

RPG TnT: 101 Dynamic Tips 'n Techniques with RPG IV by Bob Cozzi is available now.
[2] Cozzi wrote down every cool technique he's found over the years, updated Them, and consolidated them into this compact book that is your new desktop companion. The latest book from author Bob Cozzi is available today! This 300-page book contains 101 example RPG IV Tips and Techniques for everyday programming -- from date calculations, to regular express searches, to using APIs. These aren't excerpts, but full working source code examples printed right there. Order your copy today. [3]

The following is the Command Definition Source code for the SAVJOBLOG CL command. Following it is the RPG IV source code that is known as the Command Processing Program. Specify it on the PGM parameter of the CRTCMD command.

SAVJOBLOG Command Definition -- Source Type(CMD)

SAVJOBLOG:   CMD        PROMPT('Save Joblog at Job-End')
               /*   Command processing program is:  SAVJOBLOG  */
             PARM       KWD(OUTFILE) TYPE(QUAL) SNGVAL((*PRINT)) +
                          MIN(1) PROMPT('Primary/Msg details outfile')
             PARM       KWD(OUTMBR) TYPE(*NAME) DFT(*FIRST) +
                          SPCVAL((*FIRST)) EXPR(*YES) +
                          PROMPT('Primary member')
             PARM       KWD(OUTFILE2) TYPE(QUAL) SNGVAL((*NONE)) +
                          MIN(1) PROMPT('Secondary/Msg summary outfile')
             PARM       KWD(OUTMBR2) TYPE(*NAME) DFT(*FIRST) +
                          SPCVAL((*FIRST)) EXPR(*YES) +
                          PROMPT('Secondary member')
             PARM       KWD(REPLACE) TYPE(*LGL) RSTD(*YES) +
                          DFT(*REPLACE) SPCVAL((*YES '1') (*NO '0') +
                          (*REPLACE '1') (*ADD '0')) +
                          PROMPT('Replace member data')
 QUAL:       QUAL       TYPE(*NAME) MIN(1) EXPR(*YES)
             QUAL       TYPE(*NAME) DFT(*CURLIB) SPCVAL((*CURLIB) +
                          (*LIBL)) EXPR(*YES) PROMPT('Library')
             PARM       KWD(MSGFILTER) TYPE(FILTERS) MIN(0) MAX(20) +
                          PROMPT('Message filters')
 FILTERS:    ELEM       TYPE(*CHAR) LEN(10) RSTD(*YES) DFT(*ANY) +
                          SPCVAL((*ANY) (*CMD) (*COMP) (*COPY) +
                          (*DIAG) (*ESCAPE) (*ESC *ESCAPE) (*EXCP) +
                          (*EXCEPT *EXCP) (*INFO) (*INQ) (*NOTIFY) +
                          (*RQS) (*RPY)) EXPR(*YES) PROMPT('Message +
                          Type filter')
             ELEM       TYPE(*INT4) DFT(*NONE) RANGE(00 99) +
                          SPCVAL((*NONE 00)) EXPR(*YES) +
                          PROMPT('Message severity filter')
             ELEM       TYPE(*NAME) LEN(7) DFT(*ANY) SPCVAL((*ANY) +
                          (*IMMED)) EXPR(*YES) PROMPT('Message ID +
                          filter')

SAVJOBLOG RPG IV Program -- Command Processing Program

     H OPTION(*NODEBUGIO:*SRCSTMT)
     H DFTACTGRP(*NO) ACTGRP(*NEW)
     H COPYRIGHT('Copyright 2008 by Bob Cozzi')
      ********************************************************
      ** All rights reserved.
      ** Permission to use without warranty 
      ** of any kind is granted.
      ** Offered "as is" and "use at your own risk".
      *******************************************************
      /include qsysinc/qrpglesrc,qusec

     D SAVJOBLOG       PR                  extPGM('SAVJOBLOG')
     D   primary                           Const LikeDS(QUAL_T)
     D   primaryMbr                  10A   Const
     D   secondary                         Const LikeDS(QUAL_T)
     D   secondaryMbr                10A   Const
     D   bReplace                     1N   Const
     D   inFilters                         LikeDS(inFilters_T)

     D SAVJOBLOG       PI
     D   primary                           Const LikeDS(QUAL_T)
     D   primaryMbr                  10A   Const
     D   secondary                         Const LikeDS(QUAL_T)
     D   secondaryMbr                10A   Const
     D   bReplace                     1N   Const
     D   inFilters                         LikeDS(inFilters_T)


      *******************************************************************
      **  Data Structure Templates
      *******************************************************************
     D maxFilters      C                   Const(20)
     D i               S             10I 0

     D msgFilters_T    DS                  Qualified  Inz
     D  reserved1                     4A   Inz(*ALLX'00')
     D  msgsev                       10I 0
     D  msgtype                      10A
     D  msgid                         7A
     D  reserved2                     3A   Inz(*ALLX'00')

     D inFilters_T     DS                  Qualified
     D  count                         5I 0
     D  offset                        5I 0 Dim(maxFilters)
     D  filler                     1024A


     D filter_T        DS                  Qualified
     D  elemCount                     5I 0
     D  msgtype                      10A
     D  msgSev                       10I 0
     D  msgID                         7A

     D  filter         DS                  LikeDS(filter_T)

     D QUAL_T          DS                  Qualified
     D  object                       10A
     D  library                      10A
     D  obj                          10A   overlay(object)
     D  name                         10A   overlay(object)
     D  file                         10A   overlay(object)
     D  lib                          10A   overlay(library)

     D QUAL3_T         DS                  Qualified
     D  object                       10A
     D  library                      10A
     D  member                       10A
     D  obj                          10A   overlay(object)
     D  name                         10A   overlay(object)
     D  file                         10A   overlay(object)
     D  lib                          10A   overlay(library)
     D  mbr                          10A   overlay(member)
     D  mbrName                      10A   overlay(member)

     D outFile_Names_T...
     D                 DS                  Qualified
     D  length                       10I 0 Inz(%size(outFile_Names_T))
     D  primaryFile                        LikeDS(Qual3_T)
     D  pFile                              LikeDS(Qual3_T)
     D                                       overlay(primaryFile)
     D  secondaryFile                      LikeDS(Qual3_T)
     D  sFile                              LikeDS(Qual3_T)
     D                                       overlay(secondaryFile)
     D  replace                       1N


     D QMHCTLJL        PR                  extPGM('QMHCTLJL')
     D  outFiles                           Const LikeDS(outFile_Names_T)
     D  outFmt                        8A   Const
     D  msgFilters                         Const LikeDS(msgFilters_T) Dim(50)
     D  msgFilterCount...
     D                               10I 0 Const
     D  msgq                               Const LikeDS(QUAL_T)
     D  apiErrorMsg                        LikeDS(QUSEC) OPTIONS(*VARSIZE)

     D  apiFmt         C                   Const('CTLJ0100')

     D outfiles        DS                  LikeDS(outfile_Names_T) Inz(*LIKEDS)
     D msgFilter       DS                  LikeDS(msgFilters_T) Inz(*LIKEDS)
     D                                     Dim(maxFilters)
     D msgq            DS                  LikeDS(Qual_T)
     D apiErrorDS      DS                  Qualified
     D api                                 LikeDS(QUSEC)
     D  workarea                    240A

     C                   EVAL      *INLR = *ON
      /free

          msgq.obj = '*SYSOPR';
          apiErrorDS.api.qusbprv = %size(apierrorDS);
          apiErrorDS.api.qusbavl = %size(apierrorDS);

          msgFilter = *ALLX'00';
          if (%parms >= 6);
             for i = 1 to inFilters.count;
              filter = %subst(inFilters:inFilters.Offset(i)+1:%size(filter));
              msgFilter(i).msgtype = filter.msgtype;
              msgFilter(i).msgid   = filter.msgid;
              msgFilter(i).msgsev  = filter.msgsev;
             endfor;
          endif;

          outfiles.pfile.file = primary.file;
          outfiles.pfile.lib  = primary.library;
          if (%parms()>= 2);
             outfiles.pfile.mbr  = primaryMbr;
          else;
             outfiles.pfile.mbr  = '*FIRST';
          endif;
          if (%PARMS() >=3 and seconday.file <> '*NONE');
             outfiles.sfile.file = seconday.file;
             outfiles.sfile.lib  = seconday.library;
             if (%PARMS()>=4);
                outfiles.sfile.mbr  = seconday.Mbr;
             else;
                outfiles.sfile.mbr  = '*FIRST';
             endif;
          endif;
          outfiles.length = %len(outFiles);
          outfiles.replace= *ON;

          qmhctljl(outfiles : apiFMT : msgFilter : 0 : msgq : apiErrorDS);

          return;
      /end-free

Bob Cozzi, a black belt in Judo, started practicing Judo in 1974 and began to learn programming in 1975. Today, Bob runs RPG World [4], the most popular third-party, commercial RPG IV Training Event in the world. The next RPG World Conference [5] is open to all, and is coming up on May 4, 2008. If you miss RPG World, join Bob at RPG World Academy at select cities across the U.S. coming Summer 2008.

Copyright © Penton Media

Source URL: http://systeminetwork.com/article/save-job-logs-database-file-easy-way

Links:
[1] http://systeminetwork.com/author/bob-cozzi
[2] http://www.amazon.com/dp/1583041214?tag=rw0c-20&camp=14573&creative=327641&linkCode=as1&creativeASIN=1583041214&adid=0FDDKRAMASMW6SZB3462&
[3] http://www.amazon.com/dp/1583041214?tag=rw0c-20&camp=14573&creative=327641&linkCode=as1&creativeASIN=1583041214&adid=0FDDKRAMASMW6SZB3462&
[4] http://www.rpgworld.com
[5] http://www.rpgworld.com