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.