# Opentelemetry in Oracle APEX (part-1)

When preparing for my sessions this year on OpenTelemtrey within Oracle APEX I came across a couple of contributions:

*   Dominik H on the Oracle forum: [https://forums.oracle.com/ords/apexds/post/opentelemetry-in-apex-24-2-2174](https://forums.oracle.com/ords/apexds/post/opentelemetry-in-apex-24-2-2174)
    
    *   Steve Muench (Oracle) replied on this by pointing out a japanese website:  
        [https://apexugj.blogspot.com/2025/01/apex242-new-feature-opentelemetry.html](https://apexugj.blogspot.com/2025/01/apex242-new-feature-opentelemetry.html)
        

The blog is available in a gazillion languages through Google Translate, that helps a lot. Nevertheless I thought, let me rephrase Yuji in English and add English screenshots as well.

## Support for OpenTelemetry in Oracle APEX 24.2

Yuji checked the settings for OpenTelemetry, a new feature added in Oracle APEX 24.2. As far as he can tell from 2.31 Java Script Library Upgrades in the [Oracle APEX 24.2 Release Notes](https://docs.oracle.com/en/database/oracle/apex/24.2/htmrn/new-features.html#GUID-175489FB-CC24-40F6-89EF-A4A7AAFD7770) , the following libraries are built into the front end of Oracle APEX as the OpenTelemetry SDK:

2.31 JavaScript Library Upgrades

*   @opentelemetry/api 1.9.0
    
*   @opentelemetry/core 1.26.0
    
*   @opentelemetry/instrumentation 0.53.0
    
*   @opentelemetry/instrumentation-document-load 0.39.0
    
*   @opentelemetry/instrumentation-fetch 0.53.0
    
*   @opentelemetry/instrumentation-xml-http-request 0.53.0
    
*   @opentelemetry/sdk-trace-base 1.26.0
    
*   @opentelemetry/sdk-trace-web 1.26.0
    

In Oracle APEX 24.2, there is little configuration required to enable sending client-side telemetry data through OpenTelemetry.

Oracle APEX 24.2 introduces **OpenTelemetry** to **Workspace Utilities .**

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/e37848b2-b089-4fc5-9541-1d1bfa299619.png align="center")

Configure OpenTelemetry by setting the client logging service URL and token relay URL .

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/53a12215-0830-4a2f-b1e8-5a622fd6b17c.png align="center")

The APEX application's application **definition user interface** now includes a section for **OpenTelemetry**, where you set **the product family .**

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/ddb3e668-acbe-4f00-9be3-0245345b964f.png align="center")

**It was unclear to Yuji what OpenTelemetry settings settings such as Client Logging Service URL** , **Token Relay URL** , and **Product Family** corresponded to, but it appears that Oracle APEX 24.2's OpenTelemetry support is intended to send data to the observability framework provided by Oracle's SaaS products .

Since connecting to Oracle SaaS is difficult, we will implement **the client logging service URL** and **token relay URL** processing in Oracle REST Data Services to verify the operation of OpenTelemetry in Oracle APEX 24.2.

## Creating your own collector

An OpenTelemetry-enabled APEX application sends trace data from the browser in HTTP/JSON format, compressed with gzip. The Client Logging Service URL is the destination for this data. This data format is likely to be received directly by an OpenTelemetry Collector receiver.

Create a table to store the received traces. Name the table OTEL\_TRACES **.**

```sql
create table otel_traces (
    id         number generated by default on null as identity
               constraint otel_traces_id_pk primary key,
    trace      clob,
    created    date default on null sysdate
);
```

You can do this in the SQL-workshop or any other (more appropriate tool)

Now we have the table, we can create a rest-service on it:

I'll provide the scripts that create all services below as well.

**The module name** is **OpenTelemetry** and **the module base path** is **/otel/**

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/2f3cf034-5c95-4768-bfa7-7c65dbed19d1.png align="center")

Create **traces** as **a URI template** in the created module **OpenTelemetry.**

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/f006db40-927f-4a4e-ac68-f84b48ef943c.png align="center")

Register **the POST** handler in template **traces.**  
Write the following as **a source** . The received data is decompressed with gzip and the extracted JSON document is saved in the **TRACE** column of the **EBAJ\_OTEL\_TRACES table.**

```sql
declare
   l_uncompressed_blob blob;
   l_json              clob;
begin
   l_uncompressed_blob := utl_compress.lz_uncompress(:body);
   l_json := apex_util.blob_to_clob(l_uncompressed_blob);
   insert into otel_traces(trace) values(l_json);
   dbms_lob.freeTemporary(l_uncompressed_blob);
end;
```

**The client logging service** is now complete. Copy and record **the full URL** of the resource handler as you will set it as **the client logging service URL.**

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/33cf8a76-4d00-4339-9f1c-2a49ba39adce.png align="center")

## Creating the Token Relay Service

The whole process however is a bit different than we are used to as APEX / ORDS service developers. Instead of providing a client-id / secret to the application, remember we only gave APEX a "Token Relay URL". This will be a public service that APEX will call to retrieve a token.

It will then use that token to call the Client Logging Service

The process below falls apart in these steps:

1.  create rest client (Client-ID and Secret)
    
2.  store these client-credentials within APEX builder
    
3.  create the Token Relay service that will turn the client-credentials into a token
    

The OpenTelemetry implementation in Oracle APEX expects **the client logging service URL to be protected with OAuth2 authentication, and the token relay URL** is called to obtain the access token to be placed in the Authorization header.

Configure the module that implements the client logging service to require OAuth2 authentication when accessing **OpenTelemetry .**

Run the following script. It will secure the module OpenTelemetry with OAuth2 and print **the client\_id** and **client\_secret needed to access it.**

```sql
declare
  C_MODULE_NAME constant varchar2(40) := 'OpenTelemetry';
  C_ROLE_NAME   constant varchar2(40) := 'opentelemetry_client';
  C_PRIV_NAME   constant varchar2(40) := 'opentelemetry.priv';
  C_OAUTH_NAME  constant varchar2(40) := 'opentelemetry_client';
  l_priv_roles    owa.vc_arr;
  l_priv_patterns owa.vc_arr;
  l_priv_modules  owa.vc_arr;
  l_exist number;
  l_client_id     user_ords_clients.client_id%type;
  l_client_secret user_ords_clients.client_secret%type;
begin
  -- check if role C_ROLE_NAME is exist
  select count(*) 
  into l_exist 
  from user_ords_roles 
  where name = C_ROLE_NAME;
  if l_exist = 0 then
    -- create role
    ords.create_role( p_role_name => C_ROLE_NAME );
    dbms_output.put_line('Role ' || C_ROLE_NAME || ' is created.');
  else
    dbms_output.put_line('Role ' || C_ROLE_NAME || ' not created.');
  end if;
  -- check if privilege C_PRIV_NAME is exist
  select count(*) 
  into l_exist 
  from user_ords_privileges
  where name = C_PRIV_NAME;
  if l_exist = 0 then
    -- create priv
    l_priv_roles(1)   := C_ROLE_NAME;
    l_priv_modules(1) := C_MODULE_NAME;
    ords.define_privilege(
      p_privilege_name => C_PRIV_NAME
     ,p_roles    => l_priv_roles
     ,p_patterns => l_priv_patterns
     ,p_modules  => l_priv_modules
     ,p_label    => 'opentelemetry'
     ,p_description => 'priv for opentelemetry traces url'
     ,p_comments => ''
     );
    dbms_output.put_line('Privilege ' || C_PRIV_NAME || ' created.');
  else
    dbms_output.put_line('Privilege ' || C_PRIV_NAME || ' not created.');
  end if;
  -- check if oauth client is exist
  select count(*) 
  into l_exist
  from user_ords_clients
  where name = C_OAUTH_NAME;
  if l_exist = 0 then
    -- create oauth client
    oauth.create_client(
      p_name => C_OAUTH_NAME
     ,p_grant_type => 'client_credentials'
     ,p_description => 'access OpenTelemetry receivers'
     ,p_support_email   => 'your.email@example-domain.com'
     ,p_privilege_names => C_PRIV_NAME
     );
    dbms_output.put_line('OAuth client ' || C_OAUTH_NAME || ' created.');
  else
    dbms_output.put_line('OAuth client ' || C_OAUTH_NAME || ' not created.');
  end if;
  -- grant role to oauth client
  oauth.grant_client_role(
      p_client_name => C_OAUTH_NAME
      ,p_role_name  => C_ROLE_NAME
  );
  dbms_output.put_line('Role ' || C_ROLE_NAME || ' has is granted to OAuth user ' || C_OAUTH_NAME || '.');
  -- print client_id and client_secret
  select client_id
        ,client_secret
  into l_client_id
      ,l_client_secret
  from user_ords_clients
  where name = C_OAUTH_NAME;
  dbms_output.put_line('grant_type: client_credentials');
  dbms_output.put_line('client_id: ' || l_client_id);
  dbms_output.put_line('client_secret: ' || l_client_secret);
end;
/
```

Copy and record **the client\_id** and **client\_secret** that are printed as you will need them when creating **web credentials** for APEX .

```sql
declare
  C_ROLE_NAME  constant varchar2(40) := 'opentelemetry_client';
  C_PRIV_NAME  constant varchar2(40) := 'opentelemetry.priv';
  C_OAUTH_NAME constant varchar2(40) := 'opentelemetry_client';
  l_exist number;
begin
  -- delete OAuth client.
  select count(*) 
  into l_exist
  from user_ords_clients 
  where name = C_OAUTH_NAME;
  if l_exist = 1 then
    oauth.delete_client( p_name => C_OAUTH_NAME );
    dbms_output.put_line('OAuth client deleted.');
  end if;
  -- delete privilege.
  select count(*) 
  into l_exist
  from user_ords_privileges 
  where name = C_PRIV_NAME;
  if l_exist = 1 then
    ords.delete_privilege( p_name => C_PRIV_NAME );
    dbms_output.put_line('Privilege deleted.');
  end if;
  -- delete role.
  select count(*)
  into l_exist
  from user_ords_roles
  where name = C_ROLE_NAME;
  if l_exist = 1 then
    ords.delete_role( p_role_name => C_ROLE_NAME );
    dbms_output.put_line('Role is deleted.');
  end if;
end;
/
```

Open **the Workspace Utilities** Web **Credentials** and create a new **web credential.**

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/c3532135-e89f-4fbb-8668-56d4a4f7e979.png align="center")

Create **a web credential** named OpenTelemetry with **a static ID** of **OPENTELEMETRY** . Select **OAuth2 Client Credentials** as **the authentication type.**

Enter **the client\_id** value printed as a result of running the script above as **the client ID or username , and the client\_secret** value as **the client secret or password.**

That's it.

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/45e41478-c2a0-48f0-80e4-878d6be68b70.png align="center")

**A web credential** , OpenTelemetry (Static ID: **OPENTELEMETRY** ), has been created.

![](https://cdn.hashnode.com/uploads/covers/6392045c0771b6e5cf4a8714/26c58f5a-f89a-4090-8915-597d1440c621.png align="center")

Create a service in Oracle REST Data Services that uses these web credentials to obtain an access token - this service will be **your token relay .**

**Open the RESTful service** and create a module, **name** it **OpenTelemetry-Auth** and use **the base path** / **otel-auth/.**
