Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
alrikx
Participant

What is a Class Runner?

A class runner is a great tool to test code snippets and run code directly from ADT. You can even write a simple log to the console in Eclipse.

What can be improved?

When using multiple systems and complex projects I got annoyed at some point in time, that I don't know from which system the last log was, when it was written by which runner and if the last execution has finished or failed maybe.

alrikx_0-1713355929685.png

Thats when I started my own class runner that have some improved capabilities.

How does it look like?

The output looks like this:

alrikx_1-1713356469986.png

  • You see when, what, by whom and where it was started.
  • For every log entry you get a timestamp.
  • You see when the execution finished.
  • All times and dates are formatted in your personal date and time format

What about the ABAP code? Your code gets defined in the logic() method.

 

CLASS zcl_runner_demo DEFINITION
  PUBLIC
  INHERITING FROM zcl_base_runner
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    METHODS: logic REDEFINITION.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_runner_demo IMPLEMENTATION.
  METHOD logic.
    write( `Hello World` ).
    write( `this is a demo of the enhanced class runner` ).
    write( 123 ).
  ENDMETHOD.

ENDCLASS.

 

Methods write() and out->write()

The write() method has the same signature as the out->write() method, you can even access the original out->write() if needed.

 

  METHOD logic.
    write( `Hello World` ).
    write( `this is a demo of the enhanced class runner` ).
    write( 123 ).
    out->write( `bare write` ).
  ENDMETHOD.

 

alrikx_2-1713360807380.png

of course you can pass structured and table data to write(), like you can do to out->write()

 

  METHOD logic.
    DATA ls_airport type /dmo/airport.
    write( `Hello World` ).
    ls_airport = value #( airport_id = 'SAP' city = 'Dokkerland' ).
    write( ls_airport ).
    write( name = 'Info about airport' data = ls_airport ).
  ENDMETHOD.

 

alrikx_0-1713363223717.png

Uncaught exceptions

Method logic is secured against uncaught exceptions. 

 

  METHOD logic.
    write( `Hello World` ).
    raise EXCEPTION new cx_abap_invalid_name(  ).
  ENDMETHOD.

 

It logs the behaviour:

alrikx_0-1713361297623.png

How does it look under the hood?

It is basically an abstract class that wraps the original ADT class runner:

 

"! <p class="shorttext synchronized" lang="en">Base Runner</p>
"! Improved class runner with enhanced logging capabilities
CLASS zcl_base_runner DEFINITION
  PUBLIC
  ABSTRACT
  CREATE PUBLIC.

  PUBLIC SECTION.

    INTERFACES if_oo_adt_classrun .

    "! <p class="shorttext synchronized" lang="en">This method implements your logic</p>
    "! You can use {  .METH:write } with enhanced capabilities or the {  .DATA:out }->write( ) for plain logging.
    "! @raising cx_root | <p class="shorttext synchronized" lang="en">any exception not caught, will be handled in the runner.</p>
    METHODS logic ABSTRACT
      RAISING cx_root.

    CLASS-METHODS convertuuid
      IMPORTING
                str            TYPE string
      RETURNING VALUE(rv_uuid) TYPE sysuuid_c32.

  PROTECTED SECTION.

    DATA out TYPE REF TO if_oo_adt_classrun_out.

    "! <p class="shorttext synchronized" lang="en">wrapper for out->write( )</p>
    "! this enhances the default function by writing a timestamp.
    "! this method should be used in {@link .METH:logic }
    "! @parameter data | <p class="shorttext synchronized" lang="en"></p>
    "! @parameter name | <p class="shorttext synchronized" lang="en"></p>
    METHODS write
      IMPORTING
        data TYPE any
        name TYPE string OPTIONAL.

  PRIVATE SECTION.

    "! <p class="shorttext synchronized" lang="en">determine the current timestamp and returns as string</p>
    "! <ul><li>in users timezone</li><li>in users prefered format</li></ul>
    "! @parameter rv_dateandtime | <p class="shorttext synchronized" lang="en"></p>
    CLASS-METHODS getCurrentDateandTimeFormatted
      RETURNING VALUE(rv_dateandtime) TYPE string.

    "! <p class="shorttext synchronized" lang="en">draws a horizontal line on the console</p>
    "!
    "! @parameter out | <p class="shorttext synchronized" lang="en"></p>
    CLASS-METHODS horizontalLine
      IMPORTING out TYPE REF TO if_oo_adt_classrun_out.

ENDCLASS.



CLASS zcl_base_runner IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.

    me->out = out.

    horizontalline( out ).

    TRY.

        out->write( |Start on { xco_cp=>current->tenant( )->get_url( io_type = xco_cp_tenant=>url_type->ui )->get_host(  ) } runner { cl_abap_classdescr=>get_class_name( me ) } by { cl_abap_context_info=>get_user_formatted_name( ) }| &&
        | at { getcurrentdateandtimeformatted(  ) }| ).

      CATCH cx_abap_context_info_error INTO DATA(lc_context_error).
        "handle exception
        out->write( 'Error occured in determine current user:' ).
        out->write( lc_context_error->get_text(  ) ).
    ENDTRY.

    TRY.
        me->logic( ).

      CATCH cx_root INTO DATA(lc_error).
        "handle exception
        write( 'Error occured in executing the logic:' ).
        write( lc_error->get_text(  ) ).
        DATA(previous) = lc_error->previous.
        IF lc_error->previous IS BOUND.
          write( name = 'Previous' data = lc_error->previous->get_text(  ) ).
        ENDIF.


    ENDTRY.

    out->write( |Done at { getcurrentdateandtimeformatted(  ) }| ).

    horizontalline( out ).

  ENDMETHOD.

  METHOD write.

    DATA(descr_ref) = cl_abap_typedescr=>describe_by_data( data ).

    IF name IS INITIAL.

      IF descr_ref IS INSTANCE OF cl_abap_elemdescr.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }: { data }| ).
      ELSE.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }:| ).
        out->write( data = data ).
      ENDIF.

    ELSE.
      IF descr_ref IS INSTANCE OF cl_abap_elemdescr.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }: { Name }:{ data }| ).
      ELSE.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }: { Name }:| ).
        out->write( data = data ).
      ENDIF.
    ENDIF.
  ENDMETHOD.

  METHOD getcurrentdateandtimeformatted.
    DATA tsp TYPE tzntstmps.
    DATA(lo_unix_timestamp) = xco_cp=>sy->unix_timestamp( ).
    DATA(lo_moment) = lo_unix_timestamp->get_moment( xco_cp_time=>time_zone->user ).
    tsp = lo_moment->as( xco_cp_time=>format->abap )->value.
    rv_dateandtime = |{ tsp TIMESTAMP = USER }|.
  ENDMETHOD.

  METHOD horizontalline.
    out->write( repeat( val = '-' occ = 120 ) ).
  ENDMETHOD.

  METHOD convertuuid.
    DATA(lo_uuid) = xco_cp_uuid=>format->c36->to_uuid( str ).
    rv_uuid = xco_cp_uuid=>format->c32->from_uuid( lo_uuid ).
  ENDMETHOD.

ENDCLASS.

 

Summary

The improved class runner is a tool that supports SAP NetWeaverSAP S/4HANA and SAP BTP, ABAP environment, basically everywhere the class runner is supported.

I put in a function for UUID conversion that I used in one of my last projects a lot, of course its optional.

It would be easy to extend this logic for application job execution too. All you need is an additional wrapper that rewrites the write method to append an application log and calls the logic method from the application job.

Feel free to share, reuse and enhance this idea.

If you like this post or it helped you, don't hesitate to give kudos.

2 Comments
Labels in this area