
ABAP - XCO Call Stack and Tenant
How do you use the XCO classes to access the ABAP Call Stack and obtain further information about the current tenant? In this article, we'll take a closer look.
Table of contents
The XCO classes are helper classes that provide various everyday functions bundled together under a public API. Further information and an overview of the XCO libraries can be found on the overview page.
Introduction
The call stack is primarily displayed during debugging and, in rare cases, is also needed during development to determine which process a logic function was called from. There is also the Tenant object, which returns further information about the system, such as the system URL or the subaccount ID. Therefore, we want to take a closer look at both functions in this article.
Callstack
If we want to access the call stack information, we first retrieve the object and can then continue working with it. Generally, you don't need to cache the object in a variable.
DATA(stack) = xco_cp=>current->call_stack.
For our test case, we'll look at the stack in a method of the class that is directly below the MAIN method. In the debugger, the callstack would look like this:
Complete
In the first step, we read the entire callstack using the FULL method. In the next step, we need a formatter for the output. For this, we can use the XCO_CP_CALL_STACK class and access the formatter via the FORMAT attribute. Next, we can loop over the object by returning the stack as text and obtaining a Strings object via GET_LINES.
DATA(full_stack) = stack->full( ).
DATA(format_adt) = xco_cp_call_stack=>format->adt( ).
LOOP AT full_stack->as_text( format_adt )->get_lines( )->value INTO DATA(text).
out->write( text ).
ENDLOOP.
In the second version, we're expanding the formatter to include a line number option. This also provides the line number in the text at the call point. Here, you can choose between INCLUDE and SOURCE. As Include suggests, the line number is calculated based on the include. Since methods are managed in includes, you would always receive a relatively low number here, rather than the actual position in the entire class. This makes it a bit more difficult to find in ADT, since we're working with the entire class here.
DATA(full_stack) = stack->full( ).
DATA(format_source) = xco_cp_call_stack=>format->adt( )->with_line_number_flavor(
xco_cp_call_stack=>line_number_flavor->source ).
LOOP AT full_stack->as_text( format_source )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
The output for both variants would look like this.
Filter
In the second step, we don't want to have all positions of the call stack, but rather apply different filters to the various entries. Therefore, let's start directly with the UP_TO method to read the first entries of the stack. Here, you should be aware that you will only receive an entry from the call stack starting at 7. Internally, the entries belonging to the XCO class are deleted from the stack first, so you must specify a higher number.
DATA(stack_part) = stack->up_to( 8 ).
LOOP AT stack_part->as_text( format_adt )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
If we execute the method with an 8, we get the first two rows of the stack, which would then be our current method and the class's MAIN method.
We can also work across the entire stack with FROM and TO to delimit certain entries. In the next example, we use the position to read the first two entries of the stack. The function returns the same result as above (the first two entries).
LOOP AT full_stack->to->position( 2 )->as_text( format_adt )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
In addition to position, you can also search the values by pattern; two additional methods are available for this. You can find these after TO and FROM in the interface.
In the next example, we want to get all entries where the class name matches our current class. Since we are using TO and the last occurrence here, we get all entries on the stack. This would again correspond to the two entries above.
DATA(pattern) = xco_cp_call_stack=>line_pattern->method( )->where_class_name_equals( 'ZCL_BS_DEMO_XCO_CURRENT' ).
LOOP AT full_stack->to->last_occurrence_of( pattern )->as_text( format_source )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
Tenant
If we want to get information about the current tenant, we can use the TENANT object. Don't confuse the tenant with the tenant. This provides you with information about the current system, the global account and subaccount, as well as the system's URL. This makes the information particularly relevant for the cloud. You can retrieve the object via CURRENT.
DATA(tenant_info) = xco_cp=>current->tenant( ).
If we want to access the various IDs, we have access to the various methods in the interface. Here's a brief overview of the class interface.
In the first step, let's look at the various IDs we can obtain from the information system. The global account is available to us; this should actually only exist once per customer. The subaccount describes the environment in which the ABAP environment runs. The GUID is the system ID, and you can also find this in the external format in the URL. The ID appears to be the unique ID under which the system or installation runs. As you will notice from the following examples, there are different methods to get the actual values.
DATA(global_account_id) = tenant_info->get_global_account_id( )->as_string( ).
DATA(subaccount_id) = tenant_info->get_subaccount_id( )->as_string( ).
DATA(system_guid) = tenant_info->get_guid( )->value.
DATA(system_id) = tenant_info->get_id( ).
If you want to get the URL to the system, you first need to decide which URL you want to output. Here, the system distinguishes between UI components and output, as well as API endpoints. The difference is quite small. We can get the different URL types using the XCO_CP_TENANT class.
DATA(system_host_ui) = tenant_info->get_url( xco_cp_tenant=>url_type->ui )->get_host( ).
DATA(system_host_api) = tenant_info->get_url( xco_cp_tenant=>url_type->api )->get_host( ).
Here is an overview of the results from the console. Since most IDs represent accounts and systems, most of it has been obscured.
Complete example
You can find the class here under the article, but also as a commit in our GitHub repository. The class and example have been tested on the ABAP environment.
CLASS zcl_bs_demo_xco_current DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
METHODS abap_call_stack
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS tenant_info
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_xco_current IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
abap_call_stack( out ).
tenant_info( out ).
ENDMETHOD.
METHOD abap_call_stack.
DATA(stack) = xco_cp=>current->call_stack.
DATA(full_stack) = stack->full( ).
DATA(format_adt) = xco_cp_call_stack=>format->adt( ).
DATA(format_source) = xco_cp_call_stack=>format->adt( )->with_line_number_flavor(
xco_cp_call_stack=>line_number_flavor->source ).
out->write( '### Callstack ADT Format Default (All)' ).
LOOP AT full_stack->as_text( format_adt )->get_lines( )->value INTO DATA(text).
out->write( text ).
ENDLOOP.
out->write( ` ` ).
out->write( '### Callstack ADT Format with Source (All)' ).
LOOP AT full_stack->as_text( format_source )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
DATA(stack_part) = stack->up_to( 8 ).
out->write( ` ` ).
out->write( '### Callstack ADT Format (Up To)' ).
LOOP AT stack_part->as_text( format_adt )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
out->write( ` ` ).
out->write( '### Callstack ADT Format (To)' ).
LOOP AT full_stack->to->position( 2 )->as_text( format_adt )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
DATA(pattern) = xco_cp_call_stack=>line_pattern->method( )->where_class_name_equals( 'ZCL_BS_DEMO_XCO_CURRENT' ).
out->write( ` ` ).
out->write( '### Callstack ADT Format with Source (Last)' ).
LOOP AT full_stack->to->last_occurrence_of( pattern )->as_text( format_source )->get_lines( )->value INTO text.
out->write( text ).
ENDLOOP.
ENDMETHOD.
METHOD tenant_info.
DATA(tenant_info) = xco_cp=>current->tenant( ).
DATA(global_account_id) = tenant_info->get_global_account_id( )->as_string( ).
DATA(subaccount_id) = tenant_info->get_subaccount_id( )->as_string( ).
DATA(system_guid) = tenant_info->get_guid( )->value.
DATA(system_id) = tenant_info->get_id( ).
DATA(system_host_ui) = tenant_info->get_url( xco_cp_tenant=>url_type->ui )->get_host( ).
DATA(system_host_api) = tenant_info->get_url( xco_cp_tenant=>url_type->api )->get_host( ).
out->write( global_account_id ).
out->write( subaccount_id ).
out->write( system_guid ).
out->write( system_id ).
out->write( system_host_ui ).
out->write( system_host_api ).
ENDMETHOD.
ENDCLASS.
Conclusion
You have to get used to using the call stack and filtering. You can find further information on how to use the API in the documentation. The tenant offers easy access to the methods.