Skip to Main Content
Feature Request FR-4699
Product Area Developer Experience
Status OPEN

2 Voters

Enable Developers to Implement Detailed Logging of Native Component Activities When Business Data Is Sent to Browser

nikola.velinov Public
· Oct 27 2025

Idea Summary
The built-in activity log, APEX_WORKSPACE_ACTIVITY_LOG, is perfectly suited for most use cases, and it would be entirely OK to leave it as is. However, in certain scenarios - particularly those governed by stringent security auditing requirements and regulatory compliance - more granular logging becomes necessary. This is especially true when business data flows from the server to the client. In such cases, either extend the current activity log or developers should have the ability to implement and populate their own custom, detailed logging mechanism. Addressing this idea would unlock the full potential of APEX's native components for use in highly regulated environments.

Use Case
In a strictly regulated environment, it is essential to log every server-side access to business data objects (Infotypes) with the following details:

LOG_TIMESTAMP
APEX_USER
APPLICATION_ID
PAGE_ID
[...] and several other attributes that can already be captured reliably using existing capabilities.
However, the following information is currently missing and would be critical for compliance:
STATIC_ID       : The static ID of the component (which could be mapped by developers to a logical business data object or Infotype for logging purposes, rather than logging the raw static ID itself).
ACTION          : Indicates what the component is doing with "our" business data. Actions unrelated to business data - such as saving user-specific preferences or personal report settings - can be excluded. Of interest are actions that involve retrieving or exporting business data, such as fetchData or download.
ROW_COUNT       : The number of data rows transmitted to the browser. For fetchData actions, this reflects the number of rows inserted into or initially displayed in a list in the browser. For download actions, it represents the number of rows included in the downloaded file.
TOTAL_ROW_COUNT : Where applicable, configured, and actually determined by the component (e.g. in components with activated Total Row Count), this field should contain the total number of rows exactly as displayed in the browser, with applied current filtering. If not applicable or not determined, it should remain empty.
FILTER          : For fetchData or download-prepare-file actions, this should be a CLOB containing a string representation of the applied filters. For example:
                  - In Classic Reports with Faceted Search: the search items and their values,
                  - In LOVs: the search text entered by the user,
                  - In Interactive Reports or Interactive Grids: the active filters, e.g., in a format like Column_A EQ Value_1 (and) Column_B BETWEEN Value_2 AND Value_3 (and) RowFilter = Value_4
                  The exact formatting and level of detail may vary by use case, but access to the raw filter data is essential.
FILE_NAME       : For download-get-file actions, this should contain the name of the downloaded file; otherwise empty.

One such log record each time any of the following occurs:

  • The user initially views a list in an CR/IR/IG/LOV/TREE.., refreshes the list, switches reports, changes filters, or navigates to another pagination-page (when pagination is set to "Page" mode).
  • With infinite scrolling enabled: on every server request that retrieves additional data.
  • When the component calls the server to prepare a file for subsequent download.
  • When the component initiates the actual download of a (previously prepared) file.
  • This logging capability must also support scenarios involving multiple components per request. For example: In a page-rendering request, if two Classic Reports are placed on the same page and both load their data immediately (without lazy loading), two separate log records should be created

Without this fine-grained, component-level logging, the application would not receive approval to operate.

This idea is open.

Comments

Comments

  • nikola.velinov OP 42 hours ago

    There is far too much to write about this idea, so I will address it in parts due to the 4K character limit.

    Preferred Solution (Part 1)
    There are many types of data access requests in APEX:

    • Local (within the same database or via database links) and HTTP-based (in various forms),
    • DQL (select) versus DML (insert/update/delete),
    • GET (page rendering) versus POST (AJAX) requests,
    • And, of course, a wide variety of components- each with its own characteristics (some execute SELECT statements, others may use static values, etc.).

    Attempting to deliver full coverage for every possible scenario as a comprehensive, unified solution at once would be difficult. Therefore, I would like to propose a vertical approach: implement a complete, robust solution for a well-defined subset of use cases, while ensuring the design remains extensible for future enhancements.

    Specifically, I suggest in one first step focusing on local SELECT statements issued by the following native components when they retrieve "our" business data from "our" database.

    Component Types suggestion for initial implementation:

    • Select List (SL),
    • List of Values (LOV),
    • Classic Reports (CR),
    • Interactive Reports (IR),
    • Interactive Grids (IG) and
    • TreeView (TREE).

    Data Source Types suggestion for initial implementation:

    • Table,
    • SQL Query and
    • Function Returning SQL Query.

    Data Access Types suggestion for initial implementation:

    • FETCH_DATA: transfers data to the browser for the main visible part of the component
    • FETCH_DATA_WITH_PK: like FETCH_DATA but with primary keys
    • FETCH_COLUMN: when data is fetched for one/multiple columns, e.g. fetch_counts of Faceted Search/Smart Filters, sort_widget and narrow of IR, getFilterValues of IG
    • DOWNLOAD_PREPARE_FILE: preparation of a file for subsequent download of IR/IG
    • DOWNLOAD_GET_FILE: download of a file - previously prepared with DOWNLOAD_PREPARE_FILE but not necessarily, it could also be some kind of (future) direct download.
  • nikola.velinov OP 42 hours ago

    Preferred Solution (Part 2)

    The solution should meet the following guiding principles:

    1. Tamper-proof & Synchronous: Logging must occur entirely server-side, within the same request context where the SELECT executes. It must be synchronous: if an exception occurs during logging (e.g., due to a tablespace full error where the logging table resides), no data should be sent to the browser. Instead, the APEX engine should halt and return the standard unhandled exception message - ensuring data is never exposed without a corresponding log entry.

    2. Centralized Integration: Developers should enable logging at a single, well-defined point in the application - if you extend the standard activity log then declaratively or if developers may log in custom tables than a new dedicated execution hook or, ideally, the existing "Database Session Cleanup" process. This is a strong candidate because, as required in point 1, any exception thrown here already terminates request processing.

    3. Request-Bound, Not Session-Bound: The solution should correctly handle concurrent requests (e.g. initiating a large file download, then a small one with more restricted filters in the same user session). Since the smaller request may complete first, logging must be tied to the individual request context, not the user session, to avoid race conditions. Please pay attention that while developers are already able to define the storage of the session state of Items "Per Request (Memory Only)", there is currently no such method for the filters of IR/IG which are persistently stored "Per User" or "Per Session" in their own tables, but not "Per Request". Therefore, the solution should also provide the IR/IG filters used in query construction - even though they are stored in the database and one might assume developers could retrieve them on their own.

    4. Support for Request Types & Multiple Components: Should work reliably for both GET (page render) and POST (ajax) requests, including cases where multiple components trigger data access in a single request (e.g. two Classic Reports on one page without lazy load -> two distinct log records).

    5. Sufficient Contextual Detail: Each log entry should include:

      1. Component identifier (e.g. static ID),
      2. Number of rows fetched (fetchedRowCount),
      3. Total available rows (totalRowCount, if applicable and configured - no additional query if not, than empty),
      4. Filters/search conditions: search text (for LOVs), active facets (for CR with faceted search), active filters of IR/IG
    6. Performance Neutral: The solution must not degrade APEX performance (e.g. must not execute source queries more than once)

  • nikola.velinov OP 42 hours ago

    One Simple Implementation Proposal (Part 1)

    In case that the APEX activity log would not be extended, but developers are simply enabled to build their own custom log - without changes in the AppBuilder and without changes in the current APEX-datamodel (no additional APEX DB-tables nor changes to existing ones):

    1. Introduce a global variable g_component_log to hold request-scoped component logging data (hidden in the body of a new or existing package e.g. WWV_FLOW_LOG, type declaration in the Package Spec).

    g_component_log
       |_[..]_[..]              [vertical slice: future operations that are not local - not part of the initial implementation]
       |_local_dml              [vertical slice: future local DML - not part of the initial implementation (e.g. saving report configuration as ALTERNATIVE/PRIVATE report)]
       |_local_queries          [array of records – active scope for initial implementation]
           |_component_type
           |_component_static_id
           |_component_region_id
           |_component_report_id
           |_input_action
           |_input_data_access
           |_input_first_row
           |_input_max_rows
           |_input_primary_keys
           |_input_column_id
           |_input_file_id
           |_input_filter_list_of_values
           |_input_filter_faceted_search
           |_input_filter_interactive_report
           |_input_filter_interactive_grid
           |_input_filter_<other> [vertical slice: placeholder for future filter of other component type e.g. Calendar - not part of the initial implementation]
           |_output_row_count
           |_output_total_row_count
           |_output_has_more_rows
           |_output_file_id
           |_output_file_name
    

    2. Populate this variable during request processing (for both SHOW and AJAX calls), as each relevant component executes its query.

    3. Expose a new API function, e.g., APEX_UTIL.GET_COMPONENT_LOG, that simply returns this global variable.

    4. Developers can then call this function within the "Database Session Cleanup" section, format the data as needed, and persist it to their custom logging table.

  • nikola.velinov OP 41 hours ago

    One Simple Implementation Proposal (Part 2)

    Thoughts on Individual Fields

    • component_type: Always populated with one of NATIVE_SELECT_LIST, NATIVE_POPUP_LOV, NATIVE_SQL_REPORT, NATIVE_IR, NATIVE_IG or NATIVE_JSTREE. Future extensions may include NATIVE_CARDS, NATIVE_CSS_CALENDAR, NATIVE_JET_CHART, etc. - but only if explicitly requested by other users here. If a component type is not on this list, no record should be added to the local_queries array for this component.
    • component_static_id: Always populated. If the developer has not explicitly assigned a static ID to the component, assume the developer is not interested in logging this component, and thus no record should be created for it.
    • component_region_id: Populated for all region-based components (CR, IR, IG, TREE).
    • component_report_id: Populated for components that support named or saved reports (IR, IG).
    • input_action: The native action performed by the component (CR with Faceted Search/Smart Filters: reset, sort, fetch_counts, …; IR: page, sort, col_filter, filter_delete, quick_filter, set_columns, pull_toolbar, save_break, group_by_remove, filter_toggle, sort_widget, narrow, download, …; IG: fetchData, getFilterValues, download.
    • input_data_access: Indicates the nature of the data access, e.g. FETCH_DATA, FETCH_DATA_WITH_PK, FETCH_COLUMN, DOWNLOAD_PREPARE_FILE, DOWNLOAD_GET_FILE
    • input_first_row & input_max_rows: Populated only when relevant to the component (e.g. pagination settings). Otherwise left empty.
    • input_primary_keys: Populated only when the action includes primary keys (FETCH_DATA_WITH_PK). Contains the exact key values (single or composite) as submitted in the request payload. Empty otherwise.
    • input_column_id: Populated only for FETCH_COLUMN actions.
    • input_file_id: Populated only for DOWNLOAD_GET_FILE actions.
    • input_filter_list_of_values: Contains the search_text entered by the user in a LOV.
    • input_filter_faceted_search: An array of faceted search parameters (as found in the itemsToSubmit section of the APEX ajax payload). Empty if there is no faceted search defined with the component, e.g. classic report without faceted search.
    • input_filter_interactive_report: An array of records matching the structure of APEX_APPLICATION_PAGE_IR_COND (literally %ROWTYPE of it) and populated with the filter data of the current component_report_id, as captured by APEX and used for query-construction - not via a subsequent SELECT, but exactly the very same filter data. This ensures accuracy, as post-execution queries could reflect changes made during long-running requests.
    • input_filter_interactive_grid: Similarly, an array of records of APEX_APPL_PAGE_IG_RPT_FILTERS populated with the filter data of the current component_report_id, captured at the moment the query is built in WWV_FLOW_INTERACTIVE_GRID.
    • output_row_count: Always populated (except for DOWNLOAD_GET_FILE), with a value 0 or greater than 0.
    • output_total_row_count: Populated only when the component supports, is configured with activated "Total Row Count" option by developers and has computed a total row count. Otherwise empty.
    • output_has_more_rows: If apropriate for component and configuration, otherwise empty.
    • output_file_id: Populated with the internal file ID when the action is DOWNLOAD_PREPARE_FILE. Empty otherwise.
    • output_file_name: Contains the final, resolved filename as it will appear in the browser's "Save As" dialog - after any developer-defined substitutions (e.g. &<item>.) have been applied. Populated only for action DOWNLOAD_GET_FILE.
  • nikola.velinov OP 41 hours ago

    Alternative Solutions

    The solution outlined above is intended purely as an illustration. The APEX team may choose a completely different internal implementation - provided that the end result empowers developers to easily fulfill the described auditing use case. For instance, an entirely alternative approach might involve exposing the full HTTP request and response to developers, allowing them to parse this data at their own risk (bearing in mind that the HTTP structure could change with each APEX release). However, even in such a scenario, developers would still lack access to the exact IR/IG filters as they existed immediately prior to query execution - the very filters that APEX uses internally to construct the query. Another viable direction could be to introduce a declarative mechanism, enabling developers to specify which components they wish to log in detail, along with the ability to define a custom callback directly within the component’s configuration settings. Or, similarly to the "activity log" provide a "component log" stored in an APEX table, analogous to the activity log.

    Community Input

    If you as APEX developer require this logging capability for components beyond SL, LOV, CR, IR, IG and TREE, I warmly invite you to share your specific needs here: Which components do you use and wish to log in detail? What additional metadata would you need to log (e.g., which columns were displayed or included in a download or perhaps the actual query result with all result set data - I do not need such things)? Your feedback will help the APEX team prioritize the initial implementation and the future extensions. Without concrete use cases, there is no need for the APEX team to support all possible components and scenarios if nobody needs them! Similarly, if you have alternative implementation ideas, please share them.

  • hernan.fernandez OP 16 hours ago

    This feature request perfectly matches our current security and compliance requirement.

    In our environment, all APEX applications connect to the APEX database, but the actual business data resides in a separate Oracle database (SALUDSOFT).

    All data access is performed through database links (DBLINKs) from the APEX database to the remote one.

    Because of this architecture, database-level auditing only shows the DBLINK schema user (e.g., APEX_APP), and we lose visibility of the real APEX application user (APP_USER) and the context (application, page, or component) that originated the query.

    For compliance and traceability, we need to capture — in a unified audit trail — both layers of information:

    • The APEX context (APP_USER, CLIENT_IDENTIFIER, APPLICATION_ID, PAGE_ID)
    • The actual SQL statement executed (SELECT, INSERT, UPDATE, DELETE)
    • Optionally, the DBLINK or remote data source name used

    Currently, neither Unified Auditing nor APEX Debug (even at level 9) provides this information.

    Having a built-in mechanism to extend the APEX activity log — or a new component-level logging API — that exposes SQL statements along with their APEX session context would be extremely valuable for regulated and multi-database environments like ours.

    This enhancement would finally allow complete end-to-end traceability of who accessed which data, from which APEX page and component, even when the query runs through a DBLINK to another database.

    I fully support this feature and would be happy to provide real use-case details or testing feedback if needed.

    Hernan Fernandez