2025年1月21日火曜日

Oracle APEX 24.2で追加されたOpenTelemetryの対応について

Oracle APEX 24.2の新機能として追加されたOpenTelemetryの設定について確認してみました。Oracle APEX 24.2のRelease Notesの2. 31 Java Script Library Upgradesから確認できる範囲では、OpenTelemetry SDKとして、以下のライブラリがOracle APEXのフロントエンドに組み込まれています。

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

Oracle APEX 24.2で、OpenTelemetryによるクライアント側のテレメトリー・データの送信を有効にするために必要な設定は、それほど多くはありません。

Oracle APEX 24.2では、ワークスペース・ユーティリティOpenTelemetryが新設されました。


OpenTelemetryの構成としてクライアント・ロギング・サービスURLトークン・リレーURLを設定します。


APEXアプリケーションのアプリケーション定義ユーザー・インターフェースに、OpenTelemetryのセクションが追加されています。そのセクションで製品ファミリを設定します。


クライアント・ロギング・サービスURLトークン・リレーURLおよび製品ファミリといった設定がOpenTelemetryの何の設定に対応しているのか不明だったのですが、Oracle APEX 24.2のOpenTelemetryの対応は、OracleのSaaS製品が提供しているオブザーバビリティのフレームワークに送信することを想定しているようです。

OracleのSaaSに接続するのは難しいため、クライアント・ロギング・サービスURLトークン・リレーURLの処理を、Oracle REST Data Servicesで実装してOracle APEX 24.2のOpenTelemetryの動作を確認します。

最初にクライアント・ロギング・サービスを実装します。

OpenTelemetryが有効化されたAPEXアプリケーションは、ブラウザよりトレース・データをHTTP/JSON形式でgzip圧縮して送信します。クライアント・ロギング・サービスURLは、このデータの送信先になります。この形式のデータは、OpenTelemetry Collectorのreceiverでも、そのまま受信可能と思われます。

受信したトレースを保存する表を作成します。表名はEBAJ_OTEL_TRACESとします。
create table ebaj_otel_traces (
    id         number generated by default on null as identity
               constraint ebaj_otel_traces_id_pk primary key,
    trace      clob check (trace is json),
    created    date default on null sysdate
);
以下は、SQLワークショップSQLコマンドで実行した例です。


SQLワークショップRESTfulサービスより、モジュールOpenTelemetryを作成します。

モジュール名OpenTelemetryモジュール・ベース・パス/otel/とします。


作成したモジュールOpenTelemetryに、URIテンプレートとしてtracesを作成します。


テンプレートtracesに、POSTハンドラを登録します。

ソースとして以下を記述します。受信したデータをgzipで解凍し、取り出したJSONドキュメントを表EBAJ_OTEL_TRACESの列TRACEに保存します。

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 ebaj_otel_traces(trace) values(l_json);
dbms_lob.freeTemporary(l_uncompressed_blob);
end;
以上で、クライアント・ロギング・サービスは完成です。リソース・ハンドラの完全なURLクライアント・ロギング・サービスURLとして設定するため、コピーして記録しておきます。


Oracle APEXのOpenTelemetryの実装では、クライアント・ロギング・サービスURLはOAuth2による認証で保護されていることが想定されています。トークン・リレーURLは、Authorizationヘッダーに設定するアクセス・トークンを取得する際に呼び出します。

クライアント・ロギング・サービスを実装したモジュールOpenTelemetryにアクセスする際に、OAuth2による認証を要求するように構成します。

以下のスクリプトを実行します。モジュールOpenTelemetryをOAuth2で保護し、そのアクセスに必要なclient_idclient_secretを印刷します。

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 || ' has already 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 || ' is created.');
else
dbms_output.put_line('Privilege ' || C_PRIV_NAME || ' has already 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 || ' is created.');
else
dbms_output.put_line('OAuth client ' || C_OAUTH_NAME || ' has already 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;
/
印刷されたclient_idclient_secretは、APEXのWeb資格証明を作成する際に使用するため、コピーして記録しておきます。


作業の参考になるように、上記で作成したOAuth2のクライアント、権限およびロールを削除するスクリプトを掲載しておきます。

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 ' || C_OAUTH_NAME || ' is 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 ' || C_PRIV_NAME || ' is 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 ' || C_ROLE_NAME || ' is deleted.');
end if;
end;
/

ワークスペース・ユーティリティWeb資格証明を開き、新たにWeb資格証明を作成します。


作成するWeb資格証明名前OpenTelemetry静的IDOPENTELEMETRYとします。認証タイプOAuth2クライアント資格証明を選択します。

クライアントIDまたはユーザー名に、先ほどのスクリプトの実行結果として印刷されたclient_idの値、クライアント・シークレットまたはパスワードに、client_secretの値を入力します。

以上で作成します。


Web資格証明としてOpenTelemetry(静的ID: OPENTELEMETRY)が作成されました。


このWeb資格証明を使ってアクセス・トークンを取得するサービスを、Oracle REST Data Servicesで作成します。このサービスがトークン・リレーになります。

RESTfulサービスを開き、モジュールを作成します。モジュール名OpenTelemetry-Authベース・パス/otel-auth/とします。


テンプレートとしてtokenを作成します。


テンプレートtokenGETハンドラを作成します。ソース・タイプPL/SQLを選択し、ソースとして以下を記述します。access_tokenを含んだJSONドキュメントを呼び出し元に返します。このGETハンドラの完全なURLトークン・リレーURLとして設定するため、コピーして記録しておきます。

declare
l_response clob;
l_token varchar2(80);
l_expires_in integer;
begin
-- ワークスペース名は、Web資格証明OPENTELEMETRYを作成したワークスペース名に変更すること。
apex_util.set_workspace(p_workspace => 'APEXDEV');
apex_web_service.oauth_authenticate_credential(
/*
* トークンURLはワークスペース毎に決まっている。
* proto://hostname/ords/ワークスペース名 + '/oauth/token'
*/
p_token_url => 'http://localhost:8181/ords/apexdev/oauth/token'
,p_credential_static_id => 'OPENTELEMETRY'
);
l_token := apex_web_service.oauth_get_last_token;
l_expires_in := ((apex_web_service.g_oauth_token.expires - sysdate) * 84600);
l_response := apex_string.format('{ "access_token": "%s", "token_type": "bearer", "expires_in": %s }', l_token, l_expires_in);
owa_util.mime_header('application/json', true);
htp.p(l_response);
end;
view raw token_relay.sql hosted with ❤ by GitHub

このトークン・リレーURLはAPEXのセッションで保護すべきですが、難しいため保護はかけていません。


以上でクライアント・ロギング・サービスURLトークン・リレーURLの準備ができました。

ワークスペース・ユーティリティOpenTelemetryを開き、それぞれの値を設定します。


テストに使用するAPEXアプリケーションを作成します。アプリケーション自体は何でもよいのですが、サンプル・データセットEMP/DEPTをインストールしたときに作成できるアプリケーションを使うことにします。

サンプル・データセットを開き、EMP/DEPTインストールまたは更新します。


インストールまたは更新が完了した後に表示される、アプリケーションの作成ボタンをクリックします。


アプリケーション作成ウィザードが開きます。

アプリケーションの名前Test App for OpenTelemetryに変更し、アプリケーションの作成を実行します。


アプリケーションが作成されたら、アプリケーション定義ユーザー・インターフェースを開き、OpenTelemetry製品ファミリtest-appを設定します。


以上でテストの準備は完了です。

APEXアプリケーションを実行し、適当にアプリケーションを操作します。デバッグを有効にし、JavaScriptコンソールを開いてトークン・リレーURLクライアント・ロギング・サービスURLの呼び出しが失敗していないことを確認します。失敗するとtelemetry.jsでエラーが発生し、JavaScriptコンソールに出力されます。


SQLワークショップSQLコマンドを開き、表EBAJ_OTEL_TRACESにデータが書き込まれていることを確認します。

select count(*) from ebaj_otel_traces;


EBAJ_OTEL_TRACESに保存されたデータを一覧するAPEXアプリケーションを作成します。Oracle APEX 24.2で追加されたJSONソースの機能を使います。

空のAPEXアプリケーションを作成します。名前OpenTelemetry Trace Reportとします。


アプリケーションが作成されます。共有コンポーネントを開きます。


JSONソースを開きます。APEX 24.2の新機能です。


作成済みのJSONソースが一覧されます。作成をクリックします。


作成するJSONソースの名前OpenTelemetry Tracesとします。JSONソース・タイプJSON列のある表JSON列のある表としてEBAJ_OTEL_TRACESを選択します。

へ進みます。


JSON列1TRACE(Clob)を選択します。JSONスキーマは無いため設定せず、すでに保存済みのデータより、データ・プロファイルを生成します。

へ進みます。


列TRACEに保存済みのデータから、データ・プロファイルが作成されます。

IDは表EBAJ_OTEL_TRACESの主キー列なので、デフォルトで主キーチェックが入ります。列TRACEJSONドキュメントとして認識されます。

列TRACEに保存されているJSONドキュメントの属性spansに、spanオブジェクトの配列が含まれます。spans配列中のspanオブジェクトを一意に識別する属性はspan_idであるため、SPAN_ID主キーチェックを入れます。

以上の設定を行い、作成をクリックします。


JSONソースとしてOpenTelemetry Tracesが作成されました。


作成したJSONソースOpenTelemetry Tracesをソースとした、対話モード・レポートとフォームのページを作成します。

ページの作成をクリックします。


対話モード・レポートを選択します。


ページの名前Spansとし、フォーム・ページを含めるオンにします。フォーム・ページ名Span Detailとします。

データ・ソースJSONソースを選択し、JSONソースに先ほど作成したOpenTelemetry Tracesを選択します。ネストした行に列TRACEに含まれているJSON配列に当たる1.SPANSを選択します。

へ進みます。


主キー列1ID (Number)主キー列2SPAN_ID (Varchar2)が選択されます。

確認してページの作成をクリックします。


OpenTelemetryのトレースと、トレースに含まれるspanを表示する対話モード・レポートとフォームのページが作成されました。


以上でアプリケーションは完成です。

アプリケーションを実行すると、受信したスパンの一覧が表示されます。


フォームを作成しているため、編集アイコンをクリックすると詳細情報が表示されます。


すでに受信済みのJSONドキュメントよりデータ・プロファイルを生成しているため、列TRACEに保存されているJSONドキュメントに、レポートに表示されない列がある可能性があります。JSONスキーマを元にデータ・プロファイルを生成できれば一番ですが、もっと多くのデータが保存されたのちにデータ・プロファイルを生成し直しても良いでしょう。

これでOracle APEX 24.2のOpenTelemetryについて、ある程度の評価ができそうです。

今回作成した、OpenTelemetryのスキャンを一覧するAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/opentelemetry_trace_report.zip

今回の記事は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。