ABAP Cloud - Logging
In this article, let's take a look at the topic of logging and how it works under ABAP Cloud, and we'll look at the differences and similarities.
Table of contents
Logging messages and error states is an important part of development in ABAP, especially when processing is running in the background and the user wants to look at the logs. The application log has been in the system for a long time. In this article, we'll look at the similarities and differences when it comes to developing in ABAP Cloud.
Introduction
ABAP Cloud is the new development model for Clean Core and Cloud Ready applications and does away with many obsolete things in the ABAP programming language. This means that the developer has to learn a lot of new things again and stick to the released SAP APIs. In this article we take a closer look at logging in the system and how it works under ABAP Cloud. If you want to create a development in TIER-1 that requires logging and saving messages, then the following sections will be important for you.
Classic ABAP
To remind you once again how it works in classic ABAP development and what objects we use there. Basically you use the two transactions SLG0 for the configuration and SLG1 to evaluate the log. The configuration is transported through the system in order to make the log available in test and production.
If you also want to store messages in the database, you need an object and subobject for logging. You then create a handle with BAL_LOG_CREATE, which you use to accept messages with BAL_LOG_MSG_ADD and finally write the messages to the database with BAL_DB_SAVE. The functions mentioned are the classic function modules.
Creation
In the following subsections we describe the new system via ADT, but also the old way via the ABAP API, if, for example, the release is still at an old level and the appropriate ADT APIs are not available.
ABAP Development Tools
The first innovation is the creation of the log object in the editor, for this we need the ABAP development tools. And create a new object. To do this, go to the package in the Project Explorer and select “New -> Other ABAP Repository Object” from the context menu. Then we can search for log. If you don't find an entry here, you can continue with the "API" step.
The next step is to give the object a name and description. Confirm accordingly in the next step and assign the new object to a transport order.
In the last step we end up in the editor for the application log, here you can use the "Add..." button to adopt one or more sub-objects for the logging.
In an older version, the editor was a JSON file and wasn't quite as easy to maintain. Depending on the release, the editor could look more or less similar.
API
Creating it via the API is quite simple and can be done with one or two calls. To do this, you need the class CL_BALI_OBJECT_HANDLER to create an instance and provide the class with the necessary information. The same object as above is created by this call:
DATA(lo_ohandler) = cl_bali_object_handler=>get_instance( ).
lo_ohandler->create_object( iv_object = 'ZBS_DEMO_LOG_OBJECT'
iv_object_text = 'Demo Log Object'
it_subobjects = VALUE #(
( subobject = 'TEST' subobject_text = 'Subobject for Logging' ) )
iv_package = 'ZBS_DEMO_LOGGING'
iv_transport_request = '<TRANSPORT_REQUEST>' ).
Hint: If it is neither possible to create it via ADT nor via the API, your system is probably too old. You need at least an S/4 system for this.
Logging
If you would like to know which successors there are for the BAL objects, you can do this using our Cloudification Repository Viewer, where the appropriate successors will be displayed.
Generate log
To work with the log, we need the CL_BALI_LOG class to create an instance using the CREATE method. The following example:
" Create log object
DATA(lo_log) = cl_bali_log=>create( ).
" Create and set header
DATA(lo_header) = cl_bali_header_setter=>create( object = 'ZBS_DEMO_LOG_OBJECT'
subobject = 'TEST'
external_id = cl_system_uuid=>create_uuid_c32_static( )
)->set_expiry( expiry_date = CONV d( cl_abap_context_info=>get_system_date( ) + 7 )
keep_until_expiry = abap_true ).
lo_log->set_header( lo_header ).
First we create an instance of the log, then we create a header with the necessary configuration (object, subobject, external ID). In the example above, we set the expiration date to Today and add a week. Finally, we pass the header to the log instance.
Add messages
As a next step, we now want to pass messages to the API. Various objects are available for this:
- CL_BALI_MESSAGE_SETTER - Takeover of messages
- CL_BALI_FREE_TEXT_SETTER - Takeover of simple texts
- CL_BALI_EXCEPTION_SETTER - Takeover of exceptions
In the example you will find the different ways in which messages can be passed to the log:
- Message from the system variables
- Free text
- Exception
- Classic message
- BAPI messages
" System message
MESSAGE s001(zbs_demo_log) INTO DATA(ld_message).
lo_log->add_item( cl_bali_message_setter=>create_from_sy( ) ).
" Free text
DATA(lo_free) = cl_bali_free_text_setter=>create( severity = if_bali_constants=>c_severity_warning
text = 'Execution terminated, dataset not found' ).
lo_log->add_item( lo_free ).
" Exception
DATA(lo_exc) = cl_bali_exception_setter=>create( severity = if_bali_constants=>c_severity_error
exception = NEW cx_sy_zerodivide( ) ).
lo_log->add_item( lo_exc ).
" Classic Message
DATA(lo_msg) = cl_bali_message_setter=>create(
severity = if_bali_constants=>c_severity_status
id = 'ZBS_DEMO_LOG'
number = '002'
variable_1 = CONV #( cl_abap_context_info=>get_user_business_partner_id( ) ) ).
lo_log->add_item( lo_msg ).
" BAPIRET2
DATA(lo_bapi) = cl_bali_message_setter=>create_from_bapiret2( VALUE #( type = 'E'
id = 'ZBS_DEMO_LOG'
number = '002'
message_v1 = 'Dummy' ) )
lo_log->add_item( lo_bapi ).
Save logs
Finally, the log only needs to be saved. To do this we need the interface to the database with the class CL_BALI_LOG_DB and pass on our log instance. We get the handle using the corresponding method in the log.
" Save logs
cl_bali_log_db=>get_instance( )->save_log( lo_log ).
" Get the handle
rd_result = lo_log->get_handle( ).
Read messages
To read the messages from the log we can again use the database interface. To do this we need the handle from the process before it.
DATA(lo_log_db) = cl_bali_log_db=>get_instance( ).
DATA(lo_log) = lo_log_db->load_log( id_id ).
DATA(lt_items) = lo_log->get_all_items( ).
Last but not least, we output the texts to the console, along with the following source code:
LOOP AT lt_items INTO DATA(ls_item).
out->write( ls_item-item->get_message_text( ) ).
ENDLOOP.
Hint: To read the messages from the log, the appropriate authorizations are required via the authorization object S_APPL_LOG. In ABAP Cloud this should be done via an IAM app. Then attach these to a business catalog, add and assign them using the “Maintain Business Roles” app.
Constants
The IF_BALI_CONSTANTS interface provides you with various constants to classify the messages. Under C_SEVERITY_* you will find the different types of messages and can use them when creating the messages.
ABAP Environment / Public Cloud
What does the generic evaluation of the logs in the system actually look like? Unfortunately, we will be a bit disappointed here, as there is currently no evaluation for Z-Logs, such as transaction SLG1. We can save the logs, attach them to application jobs or evaluate them via the standard API in our Fiori application. Maybe SAP will deliver something here in the future and allow a general evaluation of all application logs in the system; the infrastructure would be there.
On-Premise / Private Cloud
In the classic system, where we also have access to the SAP GUI, we can still view the configuration of our logging object using the classic transaction SLG0. The special thing here is that the object is only available for display and the actual changes have to be made via ADT:
This is exactly how we can evaluate the application log (transaction SLG1) and look at the messages generated. The functionality in the background has remained the same:
Complete example
Finally, the complete coding from today's article on how to create and read messages from the application log:
CLASS zcl_bs_demo_handle_messages DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
METHODS create_log_entries
RETURNING VALUE(rd_result) TYPE if_bali_log=>ty_handle
RAISING cx_static_check.
METHODS read_log_entries
IMPORTING id_id TYPE if_bali_log=>ty_handle
RETURNING VALUE(rt_result) TYPE if_bali_log=>ty_item_table
RAISING cx_static_check.
ENDCLASS.
CLASS zcl_bs_demo_handle_messages IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
" Log messages and get handle
DATA(ld_id) = create_log_entries( ).
out->write( |Log created: { ld_id }| ).
" Read messages from handle
DATA(lt_items) = read_log_entries( ld_id ).
LOOP AT lt_items INTO DATA(ls_item).
out->write( ls_item-item->get_message_text( ) ).
ENDLOOP.
CATCH cx_root INTO DATA(lo_err).
out->write( lo_err->get_longtext( ) ).
ENDTRY.
ENDMETHOD.
METHOD create_log_entries.
" Create log object
DATA(lo_log) = cl_bali_log=>create( ).
" Create and set header
DATA(lo_header) = cl_bali_header_setter=>create( object = 'ZBS_DEMO_LOG_OBJECT'
subobject = 'TEST'
external_id = cl_system_uuid=>create_uuid_c32_static( )
)->set_expiry( expiry_date = CONV d( cl_abap_context_info=>get_system_date( ) + 7 )
keep_until_expiry = abap_true ).
lo_log->set_header( lo_header ).
" System message
MESSAGE s001(zbs_demo_log) INTO DATA(ld_message).
lo_log->add_item( cl_bali_message_setter=>create_from_sy( ) ).
" Free text
DATA(lo_free) = cl_bali_free_text_setter=>create( severity = if_bali_constants=>c_severity_warning
text = 'Execution terminated, dataset not found' ).
lo_log->add_item( lo_free ).
" Exception
DATA(lo_exc) = cl_bali_exception_setter=>create( severity = if_bali_constants=>c_severity_error
exception = NEW cx_sy_zerodivide( ) ).
lo_log->add_item( lo_exc ).
" Classic Message
DATA(lo_msg) = cl_bali_message_setter=>create(
severity = if_bali_constants=>c_severity_status
id = 'ZBS_DEMO_LOG'
number = '002'
variable_1 = CONV #( cl_abap_context_info=>get_user_business_partner_id( ) ) ).
lo_log->add_item( lo_msg ).
" BAPIRET2
DATA(lo_bapi) = cl_bali_message_setter=>create_from_bapiret2( VALUE #( type = 'E'
id = 'ZBS_DEMO_LOG'
number = '002'
message_v1 = 'Dummy' ) ).
lo_log->add_item( lo_bapi ).
" Save logs
cl_bali_log_db=>get_instance( )->save_log( lo_log ).
" Get the handle
rd_result = lo_log->get_handle( ).
ENDMETHOD.
METHOD read_log_entries.
DATA(lo_log_db) = cl_bali_log_db=>get_instance( ).
DATA(lo_log) = lo_log_db->load_log( id_id ).
rt_result = lo_log->get_all_items( ).
ENDMETHOD.
ENDCLASS.
Conclusion
Logging in ABAP Cloud is based on the same objects and techniques, but gives the whole thing a modern look in the form of classes. When it comes to evaluating messages, the cloud should add some functionality to enable the same possibilities as on-premise.