Avoid messages in business logic - SchwarzIT/sap-usi-logging-api 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.

HINT: If you should ever screw this up, check the auto-save-feature. It could be very helpful in tracking down the responsible code location. There are two ways to activate the feature:

HINT 2: 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.