ABAP Cloud - Logging
Lass uns in diesem Artikel einmal das Thema Logging anschauen und wie es unter ABAP Cloud funktioniert, dabei werden wir auf die Unterschiede und Gemeinsamkeiten eingehen.
Inhaltsverzeichnis
Logging von Meldungen und Fehlerzuständen ist in ABAP ein wichtiger Bestandteil in der Entwicklung, vor allem wenn die Verarbeitung im Hintergrund läuft und der User in die Protokolle schauen möchte. Dafür gibt es seit langer Zeit das Application Log im System. In diesem Artikel schauen wir uns die Gemeinsamkeiten und Unterschiede an, wenn es um die Entwicklung in ABAP Cloud geht.
Einleitung
ABAP Cloud ist das neue Entwicklungsmodell für Clean Core und Cloud Ready Anwendungen und verzichtet auf viele obsolete Dinge in der Programmiersprache ABAP. Das bedeutet für den Entwickler noch einmal viele Dinge neu zu lernen und sich an den freigegebenen APIs der SAP zu halten. In diesem Artikel schauen wir uns das Logging im System einmal näher an und wie es unter ABAP Cloud funktioniert. Möchtest du eine Entwicklung in TIER-1 erstellen, die ein Logging und Speichern von Nachrichten benötigt, dann werden die folgenden Abschnitte für dich wichtig werden.
Classic ABAP
Noch einmal zur Erinnerung wie es in der klassischen ABAP Entwicklung abläuft und was wir dort an Objekten verwenden. Grundsätzlich nutzt man die beiden Transaktionen SLG0 für die Konfiguration und SLG1, um das Log auszuwerten. Dabei wird die Konfiguration durch das System transportiert, um das Log auch in Test und Produktion zur Verfügung zu stellen.
Möchtest du Meldungen auch auf der Datenbank ablegen, benötigst du ein Objekt und Subobjekt fürs Logging. Im Anschluss erzeugst du dir mit BAL_LOG_CREATE ein Handle, welches du verwendest, um mit BAL_LOG_MSG_ADD Nachrichten zu übernehmen und am Ende mit BAL_DB_SAVE die Nachrichten auf die Datenbank zu schreiben. Bei den genannten Funktionen handelt es sich um die klassischen Funktionsbausteine.
Anlage
In den folgenden Unterabschnitten beschreiben wir die neue Anlage über ADT, aber auch den alten Weg über die ABAP API, wenn zum Beispiel noch das Release auf einem alten Stand ist und die passenden ADT APIs nicht verfügbar sind.
ABAP Development Tools
Die erste Neuerung ist die Anlage des Log Objekts im Editor, dafür brauchen wir die ABAP Development Tools. Und legen ein neues Objekt an. Dazu im Project Explorer auf das Paket gehen und über das Kontextmenü "New -> Other ABAP Repository Object" wählen. Dann können wir nach Log suchen, solltest du hier keinen Eintrag finden, kannst du mit dem Schritt "API" weitermachen.
Im nächsten Schritt musst du dem Objekt einen Namen und eine Beschreibung geben. Entsprechend im nächsten Schritt bestätigen und das neue Objekt einem Transportauftrag zuweisen.
Im letzten Schritt landen wir im Editor für das Application Log, hier kannst du über den "Add..." Button ein oder mehrere Unterobjekte für das Logging übernehmen.
In einer älteren Version war der Editor eine JSON Datei und nicht ganz so übersichtlich zu pflegen. Je nach Release könnte der Editor mal mehr oder weniger ähnlich aussehen.
API
Die Anlage über die API ist recht einfach und mit einem bzw. zwei Aufrufen machbar. Dazu benötigst du die Klasse CL_BALI_OBJECT_HANDLER um darüber eine Instanz zur erzeugen und die Klasse mit den nötigen Informationen zu versorgen. Das gleiche Objekt wie oben, erzeugt dieser Aufruf:
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>' ).
Hinweis: Ist weder die Anlage über ADT noch über die API möglich, hat dein System wahrscheinlich einen zu alten Stand. Du benötigst mindestens eine S/4 System dafür.
Logging
Möchtest du wissen welche Nachfolger es für die BAL Objekte gibt, dann kannst du dies über unseren Cloudification Repository Viewer machen, dort werden dir die passenden Nachfolger angezeigt.
Log erzeugen
Für die Arbeit mit dem Log benötigen wir die Klasse CL_BALI_LOG, um uns über die CREATE Methode eine Instanz zu erzeugen. Dazu einmal das folgende Beispiel:
" 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 ).
Zuerst erzeugen wir uns eine Instanz des Logs, im Anschluss erzeugen wir einen Header mit der notwendigen Konfiguration (Objekt, Unterobjekt, Externe ID). Im Beispiel oben setzen wir das Ablaufdatum auf Heute und addieren eine Woche. Zum Abschluss übergeben wir den Header an die Log Instanz.
Nachricht übernehmen
Als nächsten Schritt wollen wir nun Nachrichten an die API übergeben. Dafür stehen verschiedene Objekte zur Verfügung:
- CL_BALI_MESSAGE_SETTER - Übernahme von Nachrichten
- CL_BALI_FREE_TEXT_SETTER - Übernahme von einfachen Texten
- CL_BALI_EXCEPTION_SETTER - Übernahme von Ausnahmen
Im Beispiel findest du die verschiedenen Arten, wie Nachrichten an das Log übergeben werden können:
- Nachricht aus den Systemvariablen
- Freitext
- Ausnahme
- Klassische Nachricht
- BAPI Meldungen
" 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 ).
Log speichern
Zum Abschluss muss das Log nur noch gespeichert werden, dazu benötigen wir die Schnittstelle zur Datenbank mit der Klasse CL_BALI_LOG_DB und übergeben unsere Log Instanz. Das Handle bekommen wir über die entsprechende Methode des Logs.
" Save logs
cl_bali_log_db=>get_instance( )->save_log( lo_log ).
" Get the handle
rd_result = lo_log->get_handle( ).
Nachrichten lesen
Um die Nachrichten aus dem Log zu lesen, können wir wieder die Datenbankschnittstelle verwenden. Dazu benötigen wir das Handle aus dem Prozess davor.
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( ).
Zu guter Letzt geben wir die Texte in die Konsole aus, dazu der folgende Quellcode:
LOOP AT lt_items INTO DATA(ls_item).
out->write( ls_item-item->get_message_text( ) ).
ENDLOOP.
Hinweis: Zum Lesen der Nachrichten aus dem Log, werden die passenden Berechtigungen über das Berechtigungsobjekt S_APPL_LOG benötigt. In ABAP Cloud sollte dies über eine IAM App geschehen. Diese dann an einen Business Catalog hängen, über die App "Maintain Business Roles" aufnehmen und zuweisen.
Konstanten
Über das Interface IF_BALI_CONSTANTS stehen dir verschiedene Konstanten zur Verfügung, um die Klassifizierung der Nachrichten vorzunehmen. Unter C_SEVERITY_* findest du die verschiedenen Typen von Meldungen und kannst diese bei der Erstellung der Nachrichten verwenden.
ABAP Environment / Public Cloud
Wie sieht es eigentlich mit der generischen Auswertung der Logs im System aus? Hier werden wir leider etwas enttäuscht sein, da es im Moment keine Auswertung für Z-Logs, wie die Transaktion SLG1, gibt. Wir können die Logs speichern, können diese an Application Jobs hängen oder über die Standard API in unserer Fiori Anwendung auswerten. Vielleicht liefert SAP hier noch in Zukunft nach und erlaubt eine allgemeine Auswertung aller Application Logs im System, die Infrastruktur wäre da.
On-Premise / Private Cloud
Im klassischen System, wo wir auch Zugriff auf die SAP GUI haben, können wir noch über die klassische Transaktion SLG0 die Konfiguration unseres Logging Objekts einsehen. Die Besonderheit ist hier, dass das Objekt nur auf Anzeige steht und die eigentlichen Änderungen über ADT gemacht werden müssen:
Genau so können wir über das Application Log (Transaktion SLG1) auswerten und uns die erzeugten Meldungen ansehen. Die Funktionalität im Hintergrund ist also gleichgeblieben:
Vollständiges Beispiel
Zum Abschluss noch einmal das vollständige Coding aus dem heutigen Artikel zur Anlage und zum Lesen der Nachrichten aus dem 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.
Fazit
Logging in ABAP Cloud baut auf den gleichen Objekten und Techniken auf, gibt dem Ganzen aber in Form der Klassen ein modernes Gewand. Wenn es um die Auswertung der Nachrichten geht, sollte die Cloud noch etwas an Funktionalität zulegen, um die gleichen Möglichkeiten wie On-Premise zu ermöglichen.