Avoid messages in business logic - SchwarzIT/sap-usi-logging-api GitHub Wiki

Messages should only be sent by the main method that controls the processing.

Anti-Pattern

If your code is throwing messages all over the place, you will run into issues using the logging API.

METHOD handle_use_case.
  DATA(logger) = /usi/cl_bal_factory=>get_instance( )->create_new_logger( i_log_object  = 'ZMY_REPORT'
                                                                          i_sub_object  = 'DO_SOMETHING'
                                                                          i_external_id = i_input ).
  DATA(token) = logger->claim_ownership( ).
 
  some_bad_method( i_input ).
 
  logger->save( token ).
  logger->free( token ).
ENDMETHOD.
 
METHOD some_bad_method.
  IF i_input IS INITIAL.
    MESSAGE e000(38) WITH 'This message will break the log!'.  " <----- BAD! DON'T DO THIS!
  ENDIF.
  [...]
ENDMETHOD.

As the message statement would stop PAI-processing, method handle_use_case( ) would not reach its regular end and the log would not be saved.

This would leave you with "some random error" message, but without a log.

Troubleshooting Hint #1: Auto-Save

If something should ever break the logging, the auto-save-feature might help analyzing the issue. If the package size is set to 1, the API will immediately save every message passed to the log writer.

The malicious statement will most likely be somewhere behind the code position, that created the last log message.

There are three ways to activate the feature:

Activation type Link to documentation
Permanently / Transportable Customizing -> Log Levels -> Product specific Log Level
Temporarily / Current Setting Customizing -> Log Levels -> User specific Log Level
Temporarily / Current Setting Customizing -> Log Levels -> Client specific Log Level

The recommendation is to use User specific Log Level with package size 1 and log level 6 (Everything).

Troubleshooting Hint #2: Encapsulate what you cannot control

If you have to call legacy code or external APIs, that might send messages, you could encapsulate the call in a function module. If said function module has an exception 'ERROR_MESSAGE' will result in SY-SUBRC being <> 0. You can obtain the message text from the SY-MSG*-Fields in that case.

A better way

The following code avoids this issue by using object oriented exceptions.

METHOD handle_use_case.
  DATA(logger) = /usi/cl_bal_factory=>get_instance( )->create_new_logger( i_log_object  = 'ZMY_REPORT'
                                                                          i_sub_object  = 'DO_SOMETHING'
                                                                          i_external_id = i_input ).
  DATA(token) = logger->claim_ownership( ).

  TRY.
      some_better_method( i_input ).
    CATCH zcx_my_exception INTO DATA(the_exception).
      logger->save( token ).
      logger->free( token ).
      MESSAGE the_exception TYPE 'E'.
  ENDTRY.

  logger->save( token ).
  logger->free( token ).
ENDMETHOD.
 
METHOD some_better_method.
  IF i_input IS INITIAL.
    TRY.
        RAISE EXCEPTION TYPE zcx_my_exception
          EXPORTING
            textid = VALUE #( msgid = '38'
                              msgno = 000
                              attr1 = 'PARAM1' )
            param1 = 'NOPE!'.
      CLEANUP INTO DATA(exception).
        usi/cl_bal_factory=>get_instance( )->get_existing_logger( )->add_exception( exception ).
    ENDTRY.
  ENDIF.
  [...]
ENDMETHOD.

Method handle_use_case( ) is the one and only method that is allowed to use messages.

All methods, that are called by handle_use_case( ) are using object oriented exceptions instead.

This will ensure, that the regular end of method handle_use_case( ) will be reached, which gives it a fair chance to save the log.