ABAP log

November 22, 2007

Your fault! (Using SAP logging in your ABAP.)

Filed under: ABAP, SAP — abaplog @ 8:50 pm

The most common problem explanation we hear from users is that there should be a program error that triggers when users do everything right. While we never get rid of mistakes we do, we have to admit this special class of elusive errors that magically disappear as soon as we can trace every critical step in the program flow. The key to solution is logging. Sure, you cannot log everything, but only the most critical points. What I have to do sometimes is to add a temporary logging capability to investigate problem popping up in production system. So, let’s see what we can do to give us more chances to smack users with a log and say “Your fault!” 🙂

There are numerous simple ways to show and store messages from your program. What’s important is that they have to be stored for some time to allow analysis after they happen. For example, if it’s a report or a program that is run in background most of the time, you have obvious choices of printing messages into the spool with normal WRITEs or using MESSAGE to have it stored in the job log. Both things can be found then in SM37.

If you want logging capabilities for an online report or a dialog program, you have to use other techniques. It is the same also for user exits that are called from SAP standard transactions or things that run in background by definition, like interfaces (e.g. APO CIF).

The simplest trick I am using in development to check program logic that cannot be easily debugged is my own table, a local object, with a long text field to store my messages and probably couple of other fields like date/time to distinguish the records. The table content is written by simple MODIFY. I usually have a program that zaps the content of my logging table, to clean up the mess.

When you are going to use this kind of logging in a “real” system, some things have to be taken care of:

  1. Uniqueness of records / keys, especially in case when the messages can be written by different program instances running at the same time
  2. More intelligent way of log reorganization (schedule).

When in development system, I can simply use the current time as the table key because I know that “I’m alone”. The official way of generating unique keys in SAP, however, is using number ranges. Briefly, you have number ranges for, say, production orders, and number ranges for materials etc. For each number range object, SAP can generate you the “next number” when you call a special function. This functionality is managed centrally and numbers will be unique even if you call the function on different application servers. The disadvantage of number ranges is that they have to be customized and monitored for “exhaustion”.

To save us the effort of creating a new number range object, customizing its range and monitoring, we can use another technique of generating unique numbers (or strings) that can then be used as part of table key: Global Unique Identifiers (GUIDs). Normally this is a long string, generated to be “almost” unique for a given system. The chance of having duplicate GUIDs is so small that I have never seen any checks whether to ID exists already. SAP uses GUIDs everywhere in APO, providing tables that map readable names / codes and internal identifiers – GUIDs. Those are normally 22 or 32 characters long. You can have a GUID generated for you by calling the function GUID_CREATE. When you add date/time as key fields into your logging table, you get a pretty simple logging system without any customizing. An additional reorganization program can be run periodically to have the entries older than, say, one month, deleted. Alternatively, if some errors have to be analysed and fixed (like problems with missing master data etc), you can provide table maintenance dialog (SE11/SE54) so that responsible user can delete checked log records after analysing them.

And finally let’s have a look at the real logging, as blessed by SAP. SAP Application Log, transaction SLG1, is the most common way to store messages. Log entries are created and stored using a set of functions. Logs are grouped by log objects and subobjects. For example, you have log group for object PPORDER (production order) and you can further classify logs with production order subobjects HEADER and OPERATION. Those are selections that you use on the initial screen of SLG1, along with others like: free text name / ID, date/time, user name and transaction code.

When creating a log entry, you can tell the system how long should it be stored before it’s deleted automatically. Unlike in the above case with your own log table, you don’t need a reorganization program. I normally request the log messages to be stored for 30 days, it can be less if you feel like your system will not be happy to carry the weight.

Enough with discussion, let’s see the code now:

* this include contains log system constants and has to be used
* in your program (or top include if it's a function group)
  include sbal_constants.

* the form writes a single log entry to application log,
* building log entry identifier from parameters pf_par1 and pf_par2,
* and writing the content of other parameters into the message
  form write_log
    using pf_par1
          pf_par2
          pf_str1  type  char50
          pf_str2  type  char50
          pf_str3  type  char50
          pf_str4  type  char50.

    data:
       ls_log        type bal_s_log,
       lt_handle     type bal_t_logh,
       lf_handle     type balloghndl,
       ls_msg        type bal_s_msg.

* we use production order / operation log objects
    ls_log-object     = 'PPORDER'.
    ls_log-subobject  = 'OPERATION'.
    concatenate 'Some_Name_' pf_par1 '_' pf_par2
      into ls_log-extnumber.
    ls_log-aluser     = sy-uname.
    ls_log-alprog     = sy-repid.
    ls_log-altcode    = 'YOUR_TCODE'.
    ls_log-aldate_del = sy-datum + 30.  "keep for one month
    ls_log-del_before = 'X'.

*   create a log
    call function 'BAL_LOG_CREATE'
         exporting
              i_s_log      = ls_log
         importing
              e_log_handle = lf_handle
         exceptions
              others       = 1.

*   define data of message for Application Log
*   Use generic message template with & & & &
    ls_msg-msgty     = 'S'.
    ls_msg-msgid     = '01'.
    ls_msg-msgno     = '319'.
    ls_msg-msgv1     = pf_str1.
    ls_msg-msgv2     = pf_str2.
    ls_msg-msgv3     = pf_str3.
    ls_msg-msgv4     = pf_str4.
    ls_msg-probclass = probclass_none.

*   add this message to log
*   this function can be called several times to have one log entry
*   store several different messages
    call function 'BAL_LOG_MSG_ADD'
         exporting
              i_log_handle = lf_handle
              i_s_msg      = ls_msg
         exceptions
              others       = 1.

*   save the log
    append lf_handle to lt_handle.
    call function 'BAL_DB_SAVE'
         exporting
              i_save_all     = 'X'
              i_t_log_handle = lt_handle
         exceptions
              others         = 1.

  endform. 

Note that here we are using a generic message (01/319). This way you are free to write anything you want but then you don’t get “long text” of the message when clicking it in the SLG1. Alternatively, we could use application specific message class, assigning it and message variables to appropriate fields of the structure bal_s_msg.

2 Comments »

  1. @ilya

    Good job, explanation. Thanks.

    Comment by Tuncay Karaca — March 7, 2008 @ 9:30 pm

  2. I would like to comment your description:

    If you would like to show your saved LOG, you have to do the following:

    (0. do not forget – you can not refer with lt_handle, this is just an internal table in the global area of the function group, so if you leave your program, it has “no meaning” )

    1. Search your log with the function modul ‘BAL_DB_SEARCH’. Use the external key to find!

    2. Load the log in the memory with CALL FUNCTION ‘BAL_DB_LOAD’ ! From now, you can use the lt_handle !

    3. display the log ‘BAL_DSP_LOG_DISPLAY’ !

    see my sample code :

    REPORT zngtest1_bal_log_display .

    INCLUDE sbal_constants.

    DATA:
    ls_log TYPE bal_s_log,
    lt_handle TYPE bal_t_logh,
    ls_handle TYPE balloghndl,
    ls_msg TYPE bal_s_msg.

    DATA ls_log_filter TYPE bal_s_lfil.
    DATA:
    lt_extnumber TYPE bal_r_extn,
    ls_extnumber TYPE bal_s_extn,
    lt_log_header TYPE balhdr_t,
    ls_log_header TYPE balhdr.

    ls_extnumber-sign = ‘I’ .
    ls_extnumber-option = ‘EQ’ .

    * you have to fill here the external key !!!!
    ls_extnumber-low = ….
    APPEND ls_extnumber TO lt_extnumber.

    ls_log_filter-extnumber[] = lt_extnumber[].

    CALL FUNCTION ‘BAL_DB_SEARCH’
    EXPORTING
    i_s_log_filter = ls_log_filter
    IMPORTING
    e_t_log_header = lt_log_header
    EXCEPTIONS
    log_not_found = 1
    no_filter_criteria = 2
    OTHERS = 3.

    IF sy-subrc 0.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
    WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

    CALL FUNCTION ‘BAL_DB_LOAD’
    EXPORTING
    i_t_log_header = lt_log_header
    EXCEPTIONS
    OTHERS = 1.

    IF sy-subrc 0.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
    WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

    READ TABLE lt_log_header INDEX 1 INTO ls_log_header .
    ls_handle = ls_log_header-log_handle .
    APPEND ls_handle TO lt_handle.

    CALL FUNCTION ‘BAL_DSP_LOG_DISPLAY’
    EXPORTING
    i_t_log_handle = lt_handle
    EXCEPTIONS
    profile_inconsistent = 1
    internal_error = 2
    no_data_available = 3
    no_authority = 4
    OTHERS = 5.

    IF sy-subrc 0.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
    WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

    Comment by gnadzon — September 15, 2009 @ 5:22 pm


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.