Skip to Content
Technical Articles
Author's profile photo Enno Wulff

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.

Assigned Tags

      21 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Michael Keller
      Michael Keller

      Great Enno, I was looking for an example what to do with AMC. Et voila 🙂 Thanks.

       

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog 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... 🙁

      Author's profile photo Michael Keller
      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.

      Author's profile photo Lars Hvam
      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

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Thanks Lars! I haven't created a job for ages... 😉

      I will have a look at CL_BP_ABAP_JOB!

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Seems as if CL_BP_ABAP_JOB only works with report variants. No SUBMIT WITH = parameter possible.

      Author's profile photo Paul Hardy
      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.

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Hi Paul, you are right. The daemon services should also work but have not been chosen due to the high ABAP release.

      Author's profile photo Michael Keller
      Michael Keller

      Same situation here: No clue what to do with ABAP Daemons. Logging was my first thought. Still waiting for a new NetWeaver Trial based on 7.52 to have a look on them.

      Author's profile photo Sandra Rossi
      Sandra Rossi

      7.52 is there.

      Author's profile photo Michael Keller
      Michael Keller

      Ah, recently I installed 7.51 on AWS 🙁 Mmh, seems that I have a date ... or daemons have to wait ...

       

      Author's profile photo Frank Radmacher
      Frank Radmacher

      Hi Paul,
      Logging and lightweight background activities are only one use case for ABAP Daemons. Other use cases are, for example, to establish a robust ABAP session for queuing/scheduling of incoming events or to create a communication proxy ABAP session. I recommend the blog "ABAP Daemons – Always available ABAP sessions to handle events", which describes these use cases.

      Also note that there is no need to start ABAP Daemons programmatically anymore. ABAP Platform 1809 allows you to persist a start-up configuration, which let the ABAP system take care of the daemon startup automatically.

      Maybe also worth to mention is that a section about ABAP Daemons has been added to the ABAP Channels FAQ.

      Best regards,
      Frank

      Author's profile photo Alexander Geppart
      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.

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog 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...

      Author's profile photo Christian Guenter
      Christian Guenter

      You can use the trick with the secondary db connection also with ordinary SQL statements.

      https://help.sap.com/doc/abapdocu_752_index_htm/7.52/en-US/abenopensql_multiconnect.htm

      Author's profile photo Suhas Saha
      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

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Thanks for your comment, Suhas!

      The dashboard sounds interesting! 😉

      Author's profile photo Suhas Saha
      Suhas Saha

      In the age of Fiori dashboard, GUI-based dashboard isn’t so interesting or cool ?

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Don't say that! 8-Bit-Retro games are totally hip these days... 😀

      Author's profile photo Shai Sinai
      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.

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      in this case do not look at it as a need but as an interesting approach and a proof-of-concept. 😉