Skip to Content
Technical Articles

Parallel Logging Using ABAP Messaging Channels

There was a requirement to log complex data. That’s quite easy. Use the Application Log BAL_LOG_CREATE, add messages with BAL_LOG_MSG_ADD and save the protocol with BAL_DB_SAVE when finished.

The task then was to save the data even if a rollback was initiated.

ABAP Messaging Channels

The only way to solve this task was: AMC – ABAP messaging Channels.

The idea:

  • create a listener job in the background
  • send the messages to the listener
  • save the log data by any means in the background job

The listener runs in its own task and can do whatever it wants to do (wait, save, commit, …).

Create A Messaging Channel

To create a messaging channel you will have to start transaction SAMC and define the channels, listening program and sending program (or class).

There are different Message type IDs (Text, binary and PCP – Push Channel Protocol) where I chose TEXT for demonstration purposes.

Create A Listener

The listener can simply be created with the following code:

        cl_amc_channel_manager=>get_consumer_session_id( ).
        cl_amc_channel_manager=>create_message_consumer(
            i_application_id       = 'ZTEST'
            i_channel_id           = '/prot'
            i_channel_extension_id = CONV #( id )
            )->start_message_delivery( i_receiver = mo_current ).

I used a unique identification to make sure that each sender has it’s own listener.

To make sure that the data can be received by the listener, you will have to create a class which implements the interface IF_AMC_MESSAGE_RECEIVER_TEXT.

The interface method IF_AMC_MESSAGE_RECEIVER_TEXT~RECEIVE will receive the text string.

Create A Sender

Sending the data is also quite easy:

DATA(amc_producer) = CAST if_amc_message_producer_text(
                 cl_amc_channel_manager=>create_message_producer(
                   i_application_id       = 'ZTEST'
                   i_channel_id           = '/prot'
                   i_channel_extension_id = CONV #( id ) ) ).
amc_producer->send( i_message = 'This is an important message!' ).

Create A Job

To create the background job we use do the follwing:

  • call function module JOB_OPEN
  • SUBMIT the listener program with the unique ID
  • call function module JOB_CLOSE

Protocol

There are two logs created:

  • The main log (where all the fuzz is about) using the Application log
  • The job protocol of the listener.

The Application Log

The job protocol

Each text string is “raised” by MESSAGE TYPE “S” to appear in the job log.

Information For Usage

The following program starts itself in background (which created the listener for the given id) and then sends some messages, DOES A ROLLBACK, sends further messages and ends the communication.

Variant A – ROLLBACK

If you prepared saving data before the rollback, all data updated in the background job a re not affected.

Variant B – COMMIT

You cannot do a COMMIT in the background job (you will receive the short dump AMC_ILLEGAL_STATEMENT) to make sure the application data will be saved but you can be sure that the session will be available till the end to save the data in a controlled way.

Stop Message Delivery!

I am not sure if there should be somewhere called a STOP_MESSAGE_DELIVERY… In this case the job runs up to 10 seconds and with the end of the job, the messaging channels will also be stopped.

Code

REPORT zzenno184.

PARAMETERS batch TYPE c LENGTH 1 NO-DISPLAY.
PARAMETERS id    TYPE numc10 DEFAULT sy-uzeit.

CLASS amc DEFINITION.
  PUBLIC SECTION.
    INTERFACES if_amc_message_receiver_text.
    CLASS-METHODS listen IMPORTING id TYPE numc10.
    CLASS-METHODS create_listener_job IMPORTING id TYPE numc10.
    DATA end TYPE boolean_flg.

  PRIVATE SECTION.
    DATA mv_log_handle        TYPE balloghndl.
    DATA mv_end TYPE boolean_flg.
ENDCLASS.

CLASS amc IMPLEMENTATION.
  METHOD if_amc_message_receiver_text~receive.
    DATA ls_msg             TYPE bal_s_msg.

    MESSAGE i_message TYPE 'S'.
    IF mv_log_handle IS INITIAL.
      CALL FUNCTION 'BAL_LOG_CREATE'
        EXPORTING
          i_s_log      = VALUE bal_s_log( extnumber = id object = 'BCT1' )
        IMPORTING
          e_log_handle = mv_log_handle
        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.
    ENDIF.

    IF i_message = 'end'.
      CALL FUNCTION 'BAL_DB_SAVE'
        EXPORTING
          i_save_all       = ' '
          i_t_log_handle   = VALUE bal_t_logh( ( mv_log_handle ) )
        EXCEPTIONS
          log_not_found    = 1
          save_not_allowed = 2
          numbering_error  = 3
          OTHERS           = 4.
      IF sy-subrc <> 0.
        MESSAGE |error SAVE_LOG { sy-subrc }| TYPE 'S'.
      ENDIF.
      mv_end = abap_true.
      RETURN.
    ENDIF.

    ls_msg-msgno = '000'.
    ls_msg-msgid = 'OO'.
    ls_msg-msgty = 'I'.
    ls_msg-msgv1 = i_message.

    CALL FUNCTION 'BAL_LOG_MSG_ADD'
      EXPORTING
        i_log_handle     = mv_log_handle
        i_s_msg          = ls_msg
      EXCEPTIONS
        log_not_found    = 1
        msg_inconsistent = 2
        log_is_full      = 3
        OTHERS           = 4.
    IF sy-subrc > 0.
      MESSAGE |error MSG_ADD { sy-subrc }| TYPE 'S'.
    ENDIF.

  ENDMETHOD.

  METHOD create_listener_job.

    DATA jobcount            TYPE tbtcjob-jobcount.
    DATA ret                 TYPE i.
    DATA jobname             TYPE tbtcjob-jobname.
    DATA job_was_released    TYPE btch0000-char1.

    jobname = |PARAPROT_ID_{ id }|.

    CALL FUNCTION 'JOB_OPEN'
      EXPORTING
        jobname          = jobname
      IMPORTING
        jobcount         = jobcount
      CHANGING
        ret              = ret
      EXCEPTIONS
        cant_create_job  = 1
        invalid_job_data = 2
        jobname_missing  = 3
        OTHERS           = 4.
    IF sy-subrc <> 0 OR ret <> 0.
      WRITE: / 'JOB_OPEN failed', sy-subrc.
      RETURN.
    ENDIF.

    SUBMIT zzenno184
     WITH id    = id
     WITH batch = abap_true
     VIA JOB jobname NUMBER jobcount
     AND RETURN.

    CALL FUNCTION 'JOB_CLOSE'
      EXPORTING
        jobcount             = jobcount
        jobname              = jobname
        strtimmed            = 'X'
      IMPORTING
        job_was_released     = job_was_released
      CHANGING
        ret                  = ret
      EXCEPTIONS
        cant_start_immediate = 1
        invalid_startdate    = 2
        jobname_missing      = 3
        job_close_failed     = 4
        job_nosteps          = 5
        job_notex            = 6
        lock_failed          = 7
        invalid_target       = 8
        OTHERS               = 9.
    IF sy-subrc <> 0.
      WRITE: / 'JOB_CLOSE failed', sy-subrc.
      RETURN.
    ENDIF.

    "let the job some time to start...
    WAIT UP TO 1 SECONDS.

  ENDMETHOD.

  METHOD listen.

    DATA(lo_amc) = NEW amc( ).


    TRY.
        "Create listener Channel
        cl_amc_channel_manager=>get_consumer_session_id( ).
        cl_amc_channel_manager=>create_message_consumer(
            i_application_id       = 'ZTEST'
            i_channel_id           = '/prot'
            i_channel_extension_id = CONV #( id )
            )->start_message_delivery( i_receiver =  lo_amc ).

      CATCH cx_amc_error INTO DATA(text_exc).
        MESSAGE text_exc TYPE 'S'.
    ENDTRY.

    "Wait for messages
    WAIT FOR MESSAGING CHANNELS
         UNTIL lo_amc->mv_end = abap_true
         UP TO 10 SECONDS.

  ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.

  CASE batch.
    WHEN space.

      "Create the listener job
      amc=>create_listener_job( id ).

      TRY.
          "Prepare sending AMC messages
          DATA(amc_producer) = CAST if_amc_message_producer_text(
                 cl_amc_channel_manager=>create_message_producer(
                   i_application_id       = 'ZTEST'
                   i_channel_id           = '/prot'
                   i_channel_extension_id = CONV #( id ) ) ).
          amc_producer->send( i_message = 'This is an important message!' ).
          amc_producer->send( i_message = 'Oh no!! I have to roll back...' ).
          ROLLBACK WORK.
          amc_producer->send( i_message = 'Rollback done.' ).
          amc_producer->send( i_message = 'this is the end, don''t you know it?' ).
          amc_producer->send( i_message = 'end' ).

        CATCH cx_amc_error INTO DATA(text_exc).
          cl_demo_output=>display( text_exc->get_text( ) ).
      ENDTRY.

      "Call created protocol
      SUBMIT sbal_display WITH balobj = 'BCT1' WITH balext = id AND RETURN .

    WHEN 'X'.

      "Start the listener
      amc=>listen( id ).

  ENDCASE.

Improvement

In this simple version (proof of concept) it is only possible to send simple text strings. You can also use the message type ID “PCP” (Interface IF_AC_MESSAGE_TYPE_PCP) to send serialized complex data.

 

SAP Demo Programs

If you are interested in the ABAP Messaging Channels, have a look at the programs DEMO_RECEIVE_AMC and DEMO_SEND_AMC.

21 Comments
You must be Logged on to comment or reply to a post.