Skip to Content

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.

To report this post you need to login first.

21 Comments

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

    1. Enno Wulff Post author

      Thanks Michael!

      I also tried to use AMC for misusing an amodal cl_gui_dialogbox as a modal generic popup window. But that didn‘t work… 🙁

      (0) 
      1. Michael Keller

        Sometimes it’s funny and informative to do something wrong and sometimes not … especially when you have little time 🙂 Occasionally I should count on what goes wrong until an idea works.

        (2) 
  1. Lars Hvam

    Nice demo

    Note that there is also the possibility to use the secondary database connection when calling BAL_DB_SAVE.

    For creating background jobs recommend taking a look at class CL_BP_ABAP_JOB

    (6) 
  2. Paul Hardy

    I was wondering if this was a use case for one of those ABAP DAEMON thingies. They only exist in higher releases (7.52 plus)

    They are sort of like a batch job that you start prgramtaically and until you stop it programtically it runs forever in a dormant state, waiting to receive AMC messages.

    Try as I might I have not yet found a use case for them.

    (3) 
  3. Alexander Geppart

    Hi Enno,

    nice idea.

    However like Lars Hvam already sad. We also use the second db connection for logging even when the main business process is rolled back.

    See parameter i_2th_connection of function BAL_DB_SAVE.

    (2) 
    1. Enno Wulff Post author

      If you need to have more data then the application log is not sufficient. The sbal context only allows data of 250 bytes…

      (1) 
  4. Suhas Saha

    Hi Enno,

    Thanks for blogging about AMC.

    I used AMC’s to build a GUI container-based dashboard to monitor a very critical month-end job. Instead of polling the DB for the job stats, i used AMC’s to push the stats to the dashboard. The dashboard would listen to the AMC messages & self-refresh after a defined time interval.

    BR,

    Suhas

    (1) 
  5. Shai Sinai

    (Very) interesting approach.

    However, like others have already mentioned, there is no need to reinvent the wheel in this case: Secondary database connections are already available.

    (1) 

Leave a Reply