ABAP Development

Business Application Log (BAL) – Message Statistics

Do you have batch jobs or online applications that generate massive amounts of messages in the application log?

Do you wish there was a way to just get an overview of what messages were issued so that you can focus on the most important ones?

Do you want to compare two application logs to see what the differences are?

Let’s see, how we can do this:

Option A:

If you are on an SAP S/4 HANA on premise 2021 release, you can use the report RPMMO_ANALYZE_BAL.

Before we talk about the report, we will take a look at the application log via transaction SLG1:

SLG1 Log Display

You can see that log number 00000000000006208808 has 298 messages in it and the detail list at the bottom is getting big.

Now we go to the analysis report to condense this a little:

Initial Screen of Report RPMMO_ANALYZE_BAL

You can select by the user who created the log, the application log object ID, the creation date or the log number. We will use the log number.

Result of Log Analysis

We see that 268 out of 298 error messages were due to one particular message. Our code analysis should probably concentrate on that message first (if we want to eliminate or reduce it).

Now we compare two application logs to see the results of different program runs (assuming a batch job):

Initial Screen with Compare Option
Comparison Output

We did something right? The second application run produced fewer messages …

Option B:

You can condense the message display in the application log itself, if you know how to do it.
Unfortunately, a log comparison is not possible.

Change the ALV Layout in the SLG1 Transaction

Select the fields shown above and arrange them in the following order:

Column Arrangement and Summation

Make sure that the column ‘Number’ has summation turned on.

Save the layout.

Layout Save Dialog

Now we need to subtotal:

Subtotals

Subtotal Dialog
List with Subtotals

We are almost there. As a last step, we need to manually collapse all the details. This is a little bit cumbersome but I couldn’t find another way to do it. Make sure you do this process with a log that doesn’t have too many entries in it. Fortunately, this has to be done only once.

Collapse Selections
All Sections Collapsed

This looks similar to what the report produces, except that you don’t see the text for the message.
That you will see, when you open up a collapsed section again.

Don’t forget to save the layout in this state. The system will remember that all the sections are collapsed and the next time you apply this layout on a log, it will start out in collapsed form.

If you like the report but are not on the right release yet, here is a scaled down Z version for your use. It probably needs at least a 7.45 basis release, otherwise you might get syntax errors.

The syntax errors should not be too difficult to fix on lower level releases, but no guarantees.

REPORT zema_analyze_bal.

TABLES sscrfields.
TABLES balhdr.

CLASS lcl_bal_analyze DEFINITION DEFERRED.

DATA go_bal_analyze TYPE REF TO lcl_bal_analyze.

SELECTION-SCREEN BEGIN OF BLOCK radio WITH FRAME TITLE TEXT-rad.
  PARAMETERS p_eval TYPE xfeld RADIOBUTTON GROUP r1 USER-COMMAND dummy.
  PARAMETERS p_comp TYPE xfeld RADIOBUTTON GROUP r1.
SELECTION-SCREEN END OF BLOCK radio.

SELECTION-SCREEN BEGIN OF BLOCK sel WITH FRAME TITLE TEXT-sel.
  SELECT-OPTIONS so_uname FOR balhdr-aluser     MODIF ID sel.
  SELECT-OPTIONS so_obj   FOR balhdr-object     MODIF ID sel.
  SELECT-OPTIONS so_date  FOR balhdr-aldate     MODIF ID sel.
  SELECT-OPTIONS so_log   FOR balhdr-lognumber  MODIF ID sel.
  SELECT-OPTIONS so_lghdl FOR balhdr-log_handle NO-DISPLAY.
SELECTION-SCREEN END OF BLOCK sel.

SELECTION-SCREEN BEGIN OF BLOCK comp WITH FRAME TITLE TEXT-cmp.
  PARAMETERS p_log_a TYPE balhdr-lognumber MODIF ID cmp.
  PARAMETERS p_log_b TYPE balhdr-lognumber MODIF ID cmp.
  PARAMETERS p_diff  TYPE xfeld AS CHECKBOX MODIF ID cmp.
SELECTION-SCREEN END OF BLOCK comp.

PARAMETERS p_layo TYPE xfeld NO-DISPLAY.

CLASS lcl_bal_analyze DEFINITION
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS main.

  PROTECTED SECTION.
    TYPES BEGIN OF ty_bal_analyze.
    TYPES msgty      TYPE  syst_msgty.
    TYPES msgid      TYPE  syst_msgid.
    TYPES msgno      TYPE  syst_msgno.
    TYPES msgtxt     TYPE  natxt.
    TYPES msg_count  TYPE  balcntcum.
    TYPES cell_color TYPE  lvc_t_scol.
    TYPES END OF ty_bal_analyze.

    TYPES BEGIN OF ty_bal_compare.
    TYPES msgty           TYPE  syst_msgty.
    TYPES msgid           TYPE  syst_msgid.
    TYPES msgno           TYPE  syst_msgno.
    TYPES msgtxt          TYPE  natxt.
    TYPES msg_count       TYPE  balcntcum.
    TYPES cell_color      TYPE  lvc_t_scol.
    TYPES msg_count_log_a TYPE  balcntall.
    TYPES msg_count_log_b TYPE  balcntall.
    TYPES delta           TYPE  balcntall.
    TYPES END OF ty_bal_compare.

    TYPES ty_log_rg_tab  TYPE RANGE OF balognr.
    TYPES ty_message_tab TYPE STANDARD TABLE OF bal_s_msg WITH EMPTY KEY.

    TYPES ty_msg_count_tab TYPE SORTED TABLE OF ty_bal_analyze WITH NON-UNIQUE KEY msgty msgid msgno.

    DATA gt_msg_count_ana TYPE STANDARD TABLE OF ty_bal_analyze.
    DATA gt_msg_count_cmp TYPE STANDARD TABLE OF ty_bal_compare.
    DATA gv_deltas_exist  TYPE xfeld.

    METHODS select_bal_header
      IMPORTING it_log_rg           TYPE ty_log_rg_tab
      RETURNING VALUE(rt_logheader) TYPE bal_t_logh
      RAISING   cx_t100_msg.

    METHODS compare_messages
      RAISING cx_t100_msg.

    METHODS get_messages
      IMPORTING it_logheader       TYPE bal_t_logh
      RETURNING VALUE(rt_messages) TYPE ty_message_tab.

    METHODS eval_messages
      IMPORTING it_messages TYPE ty_message_tab.

    METHODS authority_check
      RAISING cx_t100_msg.

    METHODS get_message_count
      IMPORTING it_messages         TYPE ty_message_tab
      RETURNING VALUE(rt_msg_count) TYPE ty_msg_count_tab.

    METHODS get_message_text
      IMPORTING
        is_msg_count   TYPE ty_bal_analyze
      RETURNING
        VALUE(rv_text) TYPE t100-text.

    METHODS display_alv_count.
    METHODS display_alv_comp.

    METHODS build_fcat
      IMPORTING iv_tabname     TYPE tabname
      RETURNING VALUE(rt_fcat) TYPE lvc_t_fcat.

    METHODS set_color_code
      IMPORTING iv_msgty        TYPE syst_msgty
      RETURNING VALUE(rv_color) TYPE char1.

    METHODS set_cell_color
      IMPORTING iv_msgty             TYPE syst_msgty
      RETURNING VALUE(rt_cell_color) TYPE lvc_t_scol.

    METHODS set_color_intensity
      IMPORTING iv_msgty            TYPE syst_msgty
      RETURNING VALUE(rv_intensity) TYPE char1.

    METHODS build_sort_cat
      RETURNING VALUE(rt_sort) TYPE lvc_t_sort.

ENDCLASS.

CLASS lcl_bal_analyze IMPLEMENTATION.
  METHOD main.
    TRY.
        authority_check( ).

        IF p_comp = abap_false.
          DATA(lt_logheader) = select_bal_header( so_log[] ).
          DATA(lt_msg)       = get_messages( lt_logheader ).

        ELSE.
          compare_messages( ).
          RETURN.
        ENDIF.

      CATCH cx_t100_msg.
        RETURN.
    ENDTRY.

    eval_messages( lt_msg ).

  ENDMETHOD.

  METHOD select_bal_header.
    FREE MEMORY ID sy-repid.

    SELECT FROM balhdr
    FIELDS log_handle
    WHERE aluser     IN @so_uname
    AND   aldate     IN @so_date
    AND   object     IN @so_obj
    AND   lognumber  IN @it_log_rg
    AND   log_handle IN @so_lghdl
    INTO TABLE @rt_logheader.

    CHECK sy-subrc IS NOT INITIAL.
    EXPORT return_code FROM sy-subrc TO MEMORY ID sy-repid.
    MESSAGE |No messages found that could be analyzed for the statistics count| type 'S' DISPLAY LIKE 'E'.

    RAISE EXCEPTION NEW cx_t100_msg( ).
  ENDMETHOD.

  METHOD get_messages.
    DATA lt_msg_hdl TYPE bal_t_msgh.
    DATA ls_msg     TYPE bal_s_msg  .

    CALL FUNCTION 'BAL_DB_LOAD'
      EXPORTING
        i_t_log_handle     = it_logheader
      IMPORTING
        e_t_msg_handle     = lt_msg_hdl
      EXCEPTIONS
        no_logs_specified  = 1
        log_not_found      = 2
        log_already_loaded = 3
        OTHERS             = 4 ##FM_SUBRC_OK.

    LOOP AT lt_msg_hdl INTO DATA(ls_msg_hdl).
      CALL FUNCTION 'BAL_LOG_MSG_READ'
        EXPORTING
          i_s_msg_handle = ls_msg_hdl
        IMPORTING
          e_s_msg        = ls_msg
        EXCEPTIONS
          log_not_found  = 1
          msg_not_found  = 2
          OTHERS         = 3 ##FM_SUBRC_OK.

      CHECK sy-subrc IS INITIAL.
      APPEND ls_msg TO rt_messages.
    ENDLOOP.

  ENDMETHOD.

  METHOD eval_messages.
    DATA lt_msg_count TYPE ty_msg_count_tab.

    lt_msg_count = get_message_count( it_messages ).

    LOOP AT lt_msg_count INTO DATA(ls_msg_count).
      ls_msg_count-msgtxt     = get_message_text( ls_msg_count ).
      ls_msg_count-cell_color = set_cell_color( ls_msg_count-msgty ).

      APPEND ls_msg_count TO gt_msg_count_ana.

    ENDLOOP.

    display_alv_count( ).

  ENDMETHOD.

  METHOD set_color_intensity.
    rv_intensity = SWITCH #( iv_msgty WHEN 'S' THEN '0' ELSE '1' ).
  ENDMETHOD.

  METHOD set_color_code.
    rv_color  = SWITCH #( iv_msgty WHEN 'A' THEN col_group
                                   WHEN 'E' THEN col_negative
                                   WHEN 'W' THEN col_total
                                   ELSE col_positive ).

  ENDMETHOD.

  METHOD get_message_text.

    SELECT SINGLE FROM t100
    FIELDS text
    WHERE  sprsl = @sy-langu
    AND    arbgb = @is_msg_count-msgid
    AND    msgnr = @is_msg_count-msgno
    INTO   @rv_text .

  ENDMETHOD.


  METHOD get_message_count.

    SELECT FROM @it_messages AS msg
    FIELDS msgty, msgid, msgno, COUNT( * ) AS msg_count
    GROUP BY msgty, msgid, msgno
    ORDER BY msgty, msgid, msgno
    INTO CORRESPONDING FIELDS OF TABLE @rt_msg_count.

  ENDMETHOD.


  METHOD authority_check.
    AUTHORITY-CHECK OBJECT 'S_APPL_LOG'
      ID 'ALG_OBJECT' FIELD '*'
      ID 'ALG_SUBOBJ' FIELD '*'
      ID 'ACTVT' FIELD '03'.

    IF sy-subrc <> 0.
      MESSAGE s034(bl).
      RAISE EXCEPTION NEW cx_t100_msg( ).
    ENDIF.

  ENDMETHOD.

  METHOD compare_messages.
    CONSTANTS lc_subrc_99      TYPE sy-subrc VALUE '99'.

    DATA      ls_msg_count_cmp LIKE LINE OF gt_msg_count_cmp.

    CLEAR: so_uname[],
           so_date[],
           so_obj[],
           so_lghdl[].

    DATA(lt_log_header_a) = select_bal_header( it_log_rg = VALUE #( ( sign = 'I' option = 'EQ' low = p_log_a ) ) ).
    DATA(lt_log_header_b) = select_bal_header( it_log_rg = VALUE #( ( sign = 'I' option = 'EQ' low = p_log_b ) ) ).

    DATA(lt_msg_a) = get_messages( lt_log_header_a ).
    DATA(lt_msg_b) = get_messages( lt_log_header_b ).

    DATA(lt_msg_count_a) = get_message_count( lt_msg_a ).
    DATA(lt_msg_count_b) = get_message_count( lt_msg_b ).

    FREE: lt_log_header_a,
          lt_log_header_b,
          lt_msg_a,
          lt_msg_b.

*   Compare messages in log A with log B
    LOOP AT lt_msg_count_a INTO DATA(ls_msg_count_a).
      READ TABLE lt_msg_count_b WITH TABLE KEY msgty = ls_msg_count_a-msgty
                                               msgid = ls_msg_count_a-msgid
                                               msgno = ls_msg_count_a-msgno
                                               INTO DATA(ls_msg_count_b).
      IF sy-subrc IS NOT INITIAL.
        CLEAR ls_msg_count_b.
      ENDIF.

      ls_msg_count_cmp                 = CORRESPONDING #( ls_msg_count_a ).
      ls_msg_count_cmp-msg_count_log_a = ls_msg_count_a-msg_count.
      ls_msg_count_cmp-msg_count_log_b = ls_msg_count_b-msg_count.
      ls_msg_count_cmp-delta           = ls_msg_count_a-msg_count - ls_msg_count_b-msg_count.
      ls_msg_count_cmp-msgtxt          = get_message_text( ls_msg_count_a ).
      ls_msg_count_cmp-cell_color      = set_cell_color( ls_msg_count_a-msgty ).

      IF p_diff = abap_true.
        CHECK ls_msg_count_cmp-delta IS NOT INITIAL.
      ENDIF.

      IF ls_msg_count_cmp-delta IS NOT INITIAL.
        gv_deltas_exist = abap_true.
      ENDIF.

      APPEND ls_msg_count_cmp TO gt_msg_count_cmp.

    ENDLOOP.

*   Compare messages in log B with log A
    LOOP AT lt_msg_count_b INTO ls_msg_count_b.
      READ TABLE lt_msg_count_a WITH TABLE KEY msgty = ls_msg_count_b-msgty
                                               msgid = ls_msg_count_b-msgid
                                               msgno = ls_msg_count_b-msgno
                                               TRANSPORTING NO FIELDS.

      CHECK sy-subrc IS NOT INITIAL.  "Entry only in log B but not in log A

      ls_msg_count_cmp                 = CORRESPONDING #( ls_msg_count_b ).
      ls_msg_count_cmp-msg_count_log_b = ls_msg_count_b-msg_count.
      ls_msg_count_cmp-delta           = ls_msg_count_b-msg_count * -1.
      ls_msg_count_cmp-msgtxt          = get_message_text( ls_msg_count_b ).
      ls_msg_count_cmp-cell_color      = set_cell_color( ls_msg_count_b-msgty ).

      APPEND ls_msg_count_cmp TO gt_msg_count_cmp.

    ENDLOOP.

    FREE: lt_msg_count_a,
          lt_msg_count_b.

    IF gt_msg_count_cmp IS INITIAL AND p_diff = abap_true.
      EXPORT return_code FROM lc_subrc_99 TO MEMORY ID sy-repid.

      MESSAGE |No messages with differences found| TYPE 'S'  DISPLAY LIKE 'E'.
      RAISE EXCEPTION NEW cx_t100_msg( ).

    ENDIF.

    display_alv_comp( ).
  ENDMETHOD.

  METHOD display_alv_comp.
    DATA(lt_fcat) = build_fcat( 'GT_MSG_COUNT_CMP' ).
    DATA(lt_sort) = build_sort_cat( ).

    CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY_LVC'
      EXPORTING
        it_sort_lvc     = lt_sort
        is_layout_lvc   = VALUE lvc_s_layo( ctab_fname = 'CELL_COLOR' )
        it_fieldcat_lvc = lt_fcat
        i_save          = 'A'
        is_variant      = VALUE disvariant( report = sy-repid handle = 'COMP' )
      TABLES
        t_outtab        = gt_msg_count_cmp
      EXCEPTIONS
        program_error   = 1
        OTHERS          = 2 ##FM_SUBRC_OK.

  ENDMETHOD.

  METHOD build_fcat.

    CASE iv_tabname.
      WHEN 'GT_MSG_COUNT_ANA'.
        rt_fcat = VALUE #( ( fieldname = 'MSGTY'     datatype = 'CHAR' outputlen = 3  reptext = 'Message Type'   key = abap_true    )
                           ( fieldname = 'MSGID'     datatype = 'CHAR' outputlen = 20 reptext = 'Message ID'     key = abap_true    )
                           ( fieldname = 'MSGNO'     datatype = 'NUMC' outputlen = 3  reptext = 'Message Number' key = abap_true    )
                           ( fieldname = 'MSGTXT'    datatype = 'CHAR' outputlen = 60 reptext = 'Message Text'                      )
                           ( fieldname = 'MSG_COUNT' datatype = 'INT4' outputlen = 10 reptext = 'Number'         do_sum = abap_true )
                         ).

      WHEN OTHERS.
        rt_fcat = VALUE #( ( fieldname = 'MSGTY'           datatype = 'CHAR' outputlen = 3  reptext = 'Message Type'   key = abap_true    )
                           ( fieldname = 'MSGID'           datatype = 'CHAR' outputlen = 20 reptext = 'Message ID'     key = abap_true    )
                           ( fieldname = 'MSGNO'           datatype = 'NUMC' outputlen = 3  reptext = 'Message Number' key = abap_true    )
                           ( fieldname = 'MSGTXT'          datatype = 'CHAR' outputlen = 60 reptext = 'Message Text'                      )
                           ( fieldname = 'MSG_COUNT'       datatype = 'INT4' outputlen = 10 reptext = 'Number'         do_sum = abap_true no_out = abap_true )
                           ( fieldname = 'MSG_COUNT_LOG_A' datatype = 'INT4' outputlen = 10 reptext = 'Msg Cnt Log A'  do_sum = abap_true )
                           ( fieldname = 'MSG_COUNT_LOG_B' datatype = 'INT4' outputlen = 10 reptext = 'Msg Cnt Log B'  do_sum = abap_true )
                           ( fieldname = 'DELTA'           datatype = 'INT4' outputlen = 10 reptext = 'Delta A - B'    do_sum = abap_true )
                         ).
    ENDCASE.

  ENDMETHOD.

  METHOD display_alv_count.
    DATA(lt_fcat) = build_fcat( 'GT_MSG_COUNT_ANA' ).
    DATA(lt_sort) = build_sort_cat( ).

    CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY_LVC'
      EXPORTING
        i_callback_program = sy-repid
        it_sort_lvc        = lt_sort
        is_layout_lvc      = VALUE lvc_s_layo( ctab_fname = 'CELL_COLOR' )
        it_fieldcat_lvc    = lt_fcat
        i_save             = 'A'
        is_variant         = VALUE disvariant( report = sy-repid handle = 'CNT' )
      TABLES
        t_outtab           = gt_msg_count_ana
      EXCEPTIONS
        program_error      = 1
        OTHERS             = 2 ##FM_SUBRC_OK.

  ENDMETHOD.

  METHOD build_sort_cat.
    rt_sort    = VALUE lvc_t_sort( ( spos = '1' fieldname = 'MSGTY' up = abap_true subtot = abap_true )
                                   ( spos = '2' fieldname = 'MSGID' up = abap_true )
                                   ( spos = '3' fieldname = 'MSGNO' up = abap_true ) ).

  ENDMETHOD.

  METHOD set_cell_color.
    DATA(lv_color) = set_color_code( iv_msgty ).
    DATA(lv_int)   = set_color_intensity( iv_msgty ).

    rt_cell_color = VALUE #( color-col = lv_color color-int = lv_int ( fname = 'MSGID'   )
                                                                     ( fname = 'MSGTY' )
                                                                     ( fname = 'MSGNO' ) ).
  ENDMETHOD.

ENDCLASS.


AT SELECTION-SCREEN OUTPUT.
  LOOP AT SCREEN.
    IF p_eval = abap_true OR ( p_eval IS INITIAL AND p_comp IS INITIAL ).
      CHECK screen-group1 = 'CMP'.
    ELSE.
      CHECK screen-group1 = 'SEL'.
    ENDIF.

    screen-active = '0'.
    MODIFY SCREEN.
  ENDLOOP.

START-OF-SELECTION.
  go_bal_analyze = NEW lcl_bal_analyze( ).
  go_bal_analyze->main( ).

And here are the Text Elements (Selection Texts):

P_COMP	 Compare 2 Application Logs
P_DIFF	 Only Messages with Differences
P_EVAL	 Evaluate Application Logs
P_LOG_A	 Log number A
P_LOG_B	 Log number B
SO_DATE	 Date
SO_LOG	 Log Number
SO_OBJ	 Object
SO_UNAME User
Text Elements – Selection Texts

In the Project Manufacturing Management and Optimization Application this functionality is integrated into the delivered application logs (transactions PMMO_DISLOG, PMMO_MIGLOG and PMMO_PEGLOG). Unfortunately it was not possible to deliver it as a standard for all application logs.

Application Log in PMMO

The layout we built above is delivered as 1SAP_MSG_COUNT.