Monitoring
New in 1.147.0
The monitor interface ships with abap2UI5 1.147.0 and later. The Admin Cockpit add-on is installed from its own repository with abapGit, like every add-on.
abap2UI5 serves every roundtrip and keeps no record of any of them. That is on purpose: what to log, where, for how long and under which privacy rules is a decision of the installation, not of the framework. The framework offers two things instead:
| You are | You want | Read |
|---|---|---|
| an administrator | to see whether abap2UI5 is used, fast and safely configured, without writing code | Admin Cockpit, the add-on |
| a developer | to write your own log of roundtrips | The roundtrip monitor, the interface the add-on is built on |
The words roundtrip and app draft are explained in the Glossary.
Admin Cockpit
abap2UI5-addons/admin-cockpit answers the three questions asked before abap2UI5 goes to production: is it used, is it fast, is it safely configured? It is an abap2UI5 app itself. You install it with abapGit next to the framework and start it like any other app:
?app_start=z2ui5_cl_cockpit_app| Tab | What it shows | Needs the monitor |
|---|---|---|
| Overview | active users and roundtrips today, p95 response time, error rate, draft table size, and the last 30 days | yes |
| Apps | per app class: users, sessions, roundtrips, average and p95 time, response and model size, errors, last use — plus the apps nobody has started in N days | yes |
| Errors | grouped by app, event, exception class and first line, with every occurrence and its full exception chain — and Reproduce for an occurrence | yes; Reproduce also needs the headless frontend |
| Performance | the slowest roundtrips with their phases, and hints: a model over 1 MB, large responses, a slow p95, growing app state | yes |
| Drafts & Housekeeping | rows, age, owners and expiry of the draft table; delete expired drafts, purge the cockpit's own log | no |
| Installation & Security | version, platform, user exit, UI5 bootstrap and theme, installed add-ons — and a security traffic light: CSRF origin check, hidden error details, a CSP without 'unsafe-eval' and 'unsafe-inline', security headers, reachable developer add-ons, the cockpit's own access | no |
| Live | who is active now | partly |
| Agents | what AI agents did through the agent add-on's endpoint — shown when that add-on is installed | no; the agent add-on |
| Settings | monitor mode, slow threshold, retention, privacy mode, administrators, and the change log | no |
The tabs that need no monitor work on any installation: install, open, read the traffic light. What most of its security checks look at — the CSRF check, the CSP, the response headers — is explained on the Security page.
Installation
Which branch you need depends on your abap2UI5 release: 1.147.0 or later: branch main, then switch the monitor on. 1.146.0: branch standalone, then change one line in your handler. The steps:
Install the framework first (1.146.0 or later).
Install the cockpit with abapGit into a package of its own. Pick the branch by your abap2UI5 release:
Your abap2UI5 Branch What you get has z2ui5_if_ui5_monitormaineverything 1.146.0 or later without the monitor interface standaloneeverything through the handler line of step 4 — only the phases of a roundtrip (load, main, render) need the interface standaloneismainwithout the one class that implements the monitor interface, because abapGit always pulls a whole repository and that class cannot activate without the interface. Once your abap2UI5 has it, switch the repository tomainand pull.Start the cockpit right away with
?app_start=z2ui5_cl_cockpit_app. You should see one button, Claim the administrator role: until somebody has claimed it, the cockpit shows nothing else (see Who may open it). Press it.Feed it. Two ways, which can be combined — with both, the monitor counts and the handler line only records the request and response bodies:
- The handler line, on every release: in your own HTTP handler class (the one behind the ICF node, not a class of the framework repository) replace
z2ui5_cl_ui5_http_handler=>run( server )withz2ui5_cl_cockpit_wire=>run( server ). It runs abap2UI5, takes the time and records the roundtrip. Do this once the pull is complete and every object is active. - The monitor, with branch
main: switch it on in your user exit, as described below —cs_config-check_monitor_active = abap_trueinset_config_http_post. It adds the phases of a roundtrip. Until the exit says so, abap2UI5 calls no monitor, and the usage, error and performance tabs stay empty.
- The handler line, on every release: in your own HTTP handler class (the one behind the ICF node, not a class of the framework repository) replace
After the next click in any app, the Overview tab counts a roundtrip. The README of the add-on carries the ABAP Cloud form of the handler line.
Who may open it
The cockpit shows usage and configuration of the whole system and can delete drafts. Like every abap2UI5 app it can be started by anybody who reaches the abap2UI5 ICF node, so it checks access itself, at the top of its main( ), in this order:
- A class of your own implementing
z2ui5_if_cockpit_authdecides, when the installation has one — the first implementing class by name, found the way the user exit is found. A class that cannot be created denies. - Otherwise the administrator list on the Settings tab: only the users on it get in, everybody else sees "No authorization".
- As long as that list is empty, the cockpit shows only the claim screen — one button, Claim the administrator role for the user in front of it, and nothing of the system. The first user who presses it becomes the administrator. The claim is a row with a fixed key, so of two users claiming at the same moment exactly one wins, and it is written to the change log. Until it is claimed, the security traffic light shows the cockpit's own access in red.
Claim it right after the installation, so the administrator is you. More administrators are added on the Settings tab, and the last one cannot be removed there. A class of your own looks like this:
CLASS zcl_my_cockpit_auth DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_cockpit_auth.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_my_cockpit_auth IMPLEMENTATION.
METHOD z2ui5_if_cockpit_auth~check.
" basis administrators only
AUTHORITY-CHECK OBJECT 'S_ADMI_FCD' ID 'S_ADMI_FCD' FIELD 'ST0R'.
result = xsdbool( sy-subrc = 0 ).
ENDMETHOD.
ENDCLASS.action is DISPLAY (open the cockpit) or CHANGE (delete drafts, purge logs, change settings and administrators, reproduce errors).
When the administrator has left, one class method starts over — from a two-line report, a console class (if_oo_adt_classrun) on ABAP Cloud or the class test environment in SE24 or ADT:
" hand the cockpit to a named user ...
z2ui5_cl_cockpit_auth=>reset_admins( 'NEW_ADMIN' ).
" ... or empty the list, so the next user who opens the cockpit can claim it
z2ui5_cl_cockpit_auth=>reset_admins( ).Both commit, and both are written to the change log.
Privacy
No user names are stored by default. Users are counted under a pseudonym made from the user name and a salt that is replaced every day. So a pseudonym cannot be traced back to a name, and it cannot be linked to the same user on another day. That is why the cockpit shows "users per day" and never "users per month". Counting can be switched off entirely, or switched to plain user names.
Recording which employee used which application, when and how fast can be subject to co-determination by a works council — in Germany under § 87 (1) no. 6 BetrVG, whether or not anybody intends to evaluate individuals. The default and the "no counting" mode are designed to stay on the side of aggregate system monitoring; agree plain user names with your works council and your data protection officer before switching them on.
Housekeeping as a job
The Drafts & Housekeeping tab does two things: it deletes expired drafts through the framework's own draft store, and it purges the cockpit's rows past their retention. A background job does the same with one call, z2ui5_cl_cockpit_job=>run( ). On Standard ABAP that is a two-line report scheduled in SM36. On ABAP Cloud it is an application job.
The cockpit runs on ABAP Cloud and on Standard ABAP from 7.50, with a 7.02 downport checked in its CI, and its UI runs on UI5 1.71 and later.
The change log
Every change made through the cockpit is recorded with time, user, action and details. That covers the claim and a reset, administrators added and removed, settings saved, drafts deleted, logs purged, the housekeeping job, and every reproduced error. The Settings tab shows it, newest first. It lives in the cockpit's table Z2UI5_T_CK_AUD and is kept as long as the aggregates.
Reproducing an error
When the headless frontend from Step 12 of the walkthrough is installed, the detail of an error group offers Reproduce… for the selected occurrence. It resumes the draft the failed request came with and fires the same event again through the simulator. So the app runs exactly as it ran for the user. The cockpit then shows the exception chain, the messages the app showed and the view the roundtrip displayed.
It re-runs the app logic for real
Whatever the event writes, posts or sends happens again — as the administrator, with the administrator's authorizations, and committed if the app commits. That is why the cockpit offers it to administrators only (the CHANGE action), asks before it starts and writes every replay to the change log first.
What can be reproduced, all of it stated in the dialog:
- Stateless apps only — a stateful app keeps no draft to resume.
- Only drafts that still exist — drafts expire, after 4 hours by default.
- Only your own drafts — abap2UI5 binds a draft to its owner. The cockpit knows the owner from the user name (user tracking with plain names) or from today's pseudonym (the default, same UTC day only). Without either it still tries, and the simulator reports that there is no draft when the draft is somebody else's.
- The event, not the values — what the user typed in that roundtrip and the event's arguments are not recorded, so they are not replayed.
The cockpit names the simulator only at run time: it activates without it and simply does not offer the button.
Agents
When the agent add-on is installed, the Agents tab reads its audit log and settings — at run time, with no dependency in either direction:
- whether the endpoint is enabled, the apps opted in, the app classes allowed or denied, and the agent administrators;
- calls per day, per app and per MCP client. Each number is split into two groups. Calls refused by policy: endpoint disabled, app not enabled for agents, event forbidden or reserved for a human. Calls refused or failed otherwise (validation): a wrong field or value, an unknown or expired session, an exception in the app;
- the last calls with operation, event, outcome and text — the user only with user tracking set to plain names.
The security traffic light gets a line for it. A disabled endpoint is green. An enabled one without an agent administrator is red. One that allows every app class, or reaches no app at all, is yellow.
What the cockpit records
The add-on's monitor class maps the roundtrip structure by name and hands it to a recorder. The recorder writes aggregates per day, hour, app and event: counts, errors, the time of each phase, sizes and a latency histogram the p95 is read from. It also writes one raw row for each failed or slow roundtrip. The mode decides how much: ALL, SAMPLE (every error and a share of the rest), ERRORS or OFF. Its own tables purge themselves by retention once a day.
The aggregates are added up without row locks. A roundtrip lands in the row of its hour with one UPDATE … SET cnt = cnt + 1, …. That statement is atomic on every database, so two work processes never lose each other's counts and nobody waits. The sums (milliseconds and kilobytes) are DEC 15 columns. An INT4 overflows at 2.1 billion, which the milliseconds of one busy app can reach within an hour. INT8 does not exist below 7.50. DEC 15 exists on every release and on ABAP Cloud. Increments are capped at the largest value the column holds rather than overflowing, and totals over several days are shown capped the same way.
It follows the LUW rule: for a stateful app it writes nothing and keeps the entry in the roll area until the first roundtrip there that is not stateful. A session that ends while still stateful loses those entries. That is the price of never touching the app's LUW.
The roundtrip monitor
For developers who write their own log. An administrator who installs the cockpit needs only Switch it on from this part.
z2ui5_if_ui5_monitor is an interface in the released API. Implement it in a class of your own, in a package of your own. abap2UI5 finds the class the way it finds the user exit: it takes the first class implementing the interface, sorted by name, and looks it up once per roll area. Its one method, on_roundtrip, is called once per POST roundtrip, after the response is built — on success and on failure alike.
It is not called for the page request (GET), for HEAD, or for a POST the CSRF check rejected: none of them runs an app.
Switch it on
The monitor is off until the user exit switches it on. The switch has no default value: without it the framework does not even look for an implementing class. Set check_monitor_active in set_config_http_post of your user exit:
METHOD z2ui5_if_ui5_exit~set_config_http_post.
cs_config-check_monitor_active = abap_true.
ENDMETHOD.The field is read on every roundtrip, so setting it back to abap_false stops the monitor on the next one — in a running stateful session too.
Switch the monitor on only after its package is pulled completely, active and free of syntax errors. The reason: a monitor is code of another package that runs inside every roundtrip of every app. What the method raises is caught (below). A syntax error in the monitor class, or in any class it uses, is different: it is the runtime error SYNTAX_ERROR, a short dump that no CATCH stops, on every request of every user.
What a roundtrip reports
on_roundtrip receives one structure, is_roundtrip:
| Field | Content |
|---|---|
app | the app class that answered, upper case — or the one that failed; empty when the request failed before an app was resolved |
event | the event of this roundtrip, empty on a first start or a navigation |
draft_id, draft_id_prev | the draft id the response carries and the one the request came with; draft_id is empty on failure, draft_id_prev on a first start |
uname | the user |
check_sticky | abap_true when the app runs in a stateful session — this decides the LUW, below |
check_start | the first request of an app start |
timestampl | when the roundtrip started, UTC |
ms_total, ms_load, ms_main, ms_render | milliseconds in total and per phase: reading the request and the draft, the app's main( ), building the response and saving the draft |
bytes_request, bytes_response, bytes_model | the sizes of the JSON strings in characters; the response and the model are 0 on failure |
ms_client_prev | how long the previous roundtrip took as the browser measured it, sent along with this request; 0 when unknown |
check_error, error_text, error_class | whether it failed, the full exception chain as the error response renders it — with the same error id the user sees — and the class of the root cause |
An implementation
A monitor that keeps the slow and the failed roundtrips in a table of your own:
CLASS zcl_my_roundtrip_monitor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_ui5_monitor.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_my_roundtrip_monitor IMPLEMENTATION.
METHOD z2ui5_if_ui5_monitor~on_roundtrip.
" only what is worth reading later
IF is_roundtrip-check_error = abap_false AND is_roundtrip-ms_total < 2000.
RETURN.
ENDIF.
" a stateful app owns the LUW - committing here would commit its work
IF is_roundtrip-check_sticky = abap_true.
RETURN.
ENDIF.
TRY.
" zmy_rt_log is a table of your own
DATA(ls_log) = VALUE zmy_rt_log( app = is_roundtrip-app
event = is_roundtrip-event
uname = is_roundtrip-uname
timestampl = is_roundtrip-timestampl
ms_total = is_roundtrip-ms_total
error_class = is_roundtrip-error_class ).
INSERT zmy_rt_log FROM @ls_log.
COMMIT WORK.
CATCH cx_root ##NO_HANDLER.
" a monitor never breaks an app
ENDTRY.
ENDMETHOD.
ENDCLASS.The rules an implementation lives by
- It is synchronous. Whatever the method costs, the user waits for. Keep it to a few single-row statements and decide early what to skip.
- Its errors are ignored. Anything the method raises is caught and ignored, and so is a class that cannot be instantiated: a broken monitor loses its log entries and never stops the app. The user exit does the opposite: an exit that cannot run stops the request with an error, because it carries security settings. A monitor only watches.
- The LUW depends on
check_sticky. For a stateless app (check_stickyisabap_false) the LUW is empty when the monitor runs. On success the framework has committed its draft save. On failure it rolled the LUW back before the call. ACOMMIT WORKin the monitor commits its own writes and nothing of the app. For a stateful app the LUW belongs to the app, which may hold uncommitted work and locks across roundtrips. Do not commit then. Keep the entry and write it on a later roundtrip that is stateless, or through a database connection of its own. - Commit yourself. The framework offers no commit helper; an implementation issues
COMMIT WORKitself. - One monitor is called. With two implementing classes on a system, the first by name wins. That is the same rule as for the user exit, so the choice does not change after a transport or a system copy. A monitor that has to feed two consumers calls the second one itself.
While the switch is off nothing changes at all: the exit is asked for the flag and the class repository is not consulted. With the switch on and no implementing class, the lookup runs once per roll area and finds nothing. A host without a class repository — the framework running on Node — and a unit test install a monitor directly with z2ui5_cl_ui5_srv_monitor=>set_monitor( ); a monitor installed that way is the host's choice and bypasses the switch.
Next Steps
- User Exits — the other interface the framework finds on its own, and the one whose errors stop the request
- Security — what the cockpit's traffic light checks
- Performance — what to do about a slow p95
- Agent-Operable Apps — the same roundtrip, driven by an AI agent instead of a browser
