📘

Splunk RUMでネイティブアプリとWebviewのRUMセッション統合を試してみた

に公開

オブザーバビリティの向上におけるモチベーションの1つとして、フロントエンドやモバイルアプリで起きていることを知りたい、という要求をよく耳にします。

バックエンドで起きていることは、最悪ログを丹念にたどっていけば事象を把握できます。しかし、ユーザーサイドで起きているエラーを、環境情報含めヒアリングで明らかにしていく過程は大きな労力がかかります。多数の端末で散発的に同じエラーが起こるようなシチュエーションでは、とても人手で賄いきれるものではありません。

このような場合、モバイルRUMが役立ちます。

ネイティブアプリケーションにRUMエージェントを含んだ状態でデプロイすることで、ユーザーの操作や端末情報の一部を取得できます。場合によっては操作のリプレイを使って、どのようにモバイルアプリが使われ、エラーが発生したか確認できます。

WebViewアプリの計装

モバイルアプリには様々な開発手法があります。モバイルアプリ内でWEBアプリを表示するもその1つです。アプリ機能の大半がWebvViewで表示されている場合、RUMはWEBアプリと同じように実装できます。

しかし、皆さんにも見覚えがあるように、現実にはモバイルアプリ特有の機能を制御するネイティブアプリ部分と、Webサイトを共通の内容を表示するWebViewを併用するアプリケーションアーキテクチャが広く採用されています。

WEBサービスと共通の機能を提供しつつ開発工数を節約するための合理的な手法ですが、オブザーバビリティの観点から見た場合、ネイティブアプリ部分とWebView部分の操作を一連のイベントとして考える必要があります。

RUMとは

Real User Monitoring (RUM) は実ユーザー端末から以下の種類のテレメトリを収集し、ユーザー体験を可視化します。

  • パフォーマンス: ページ表示/画面遷移時間、リソースロード
  • 安定性: JavaScriptエラー、クラッシュ、例外
  • 行動: クリック/タップなどカスタムイベント
  • 端末/環境: OSバージョン、機種情報、ブラウザ、キャリア

セッションは「一定の非操作時間(Splunk Observability Cloudの場合15分)」「アプリ再起動」などで区切られます。ハイブリッド構成ではネイティブとWebViewで別々に生成されると分析や課金が分散し非効率になるため、これらの統合が重要です。

ネイティブアプリセッションとWebViewセッションの統合

Splunk Observability CloudのRUMでは、ネイティブアプリ部分とWebView部分のセッションIDを統合する機能があります。

もしセッションが統合されない場合は、せっかくのRUM機能で取得したユーザーアクティビティが途切れてしまうほか、セッション数がダブルカウントされてサブスクリプション使用料的にも好ましくありません。

動作を確認するために、簡単なAndroidアプリを作成して両者が単一セッションとして扱われることを確認していきます。

なお、筆者は大昔(Android2.x時代)にTwitterクライアントを開発しようとした以来のAndroidアプリ開発となるので、GitHub Copilotの力を大いに活用してアプリケーションを作成していきます。

基本的なSplunk Observability RUMの組み込み方については割愛するので、この点を知りたい方は同僚の記事をご参照ください。

https://zenn.dev/kntr_nkgm/articles/3bccee474bdb59

サンプルAndroidアプリを作成する

今回作成するテストアプリのポイントは3点です。

  • ネイティブアプリとして実装される機能と、WebViewで実装している機能の両方が存在する
  • それぞれの動作について、ネイティブとWebViewを判定できるAttributeをテレメトリデータに含める
  • Splunk Mobile RUM SDKを用いて、セッションをWebViewに渡してSplunk Observability Cloud上で同一セッションとして扱われるか確認する

GitHub Copilotのエージェントモードを利用して、GPT-5-Codexに上記要件を満たしたプロジェクトを作成してもらいます。

各ボタンでRUMのカスタムイベントが記録される仕組み

Splunk RUM ハイブリッドアプリにおけるセッション共通化の実装

ネイティブとWebViewで単一のRUMセッションを共有するためのポイントを解説します。

1. Application初期化でのMobile RUM設定

アプリケーション起動時に、Mobile RUM SDKを初期化します。これは、モバイルRUMの基本的な使い方です。

ここでは各設定値をハードコードしていますが、実際はビルド時に置き換えたいことも多い(特にEnvironment)ので、実プロダクトでは環境変数から読み込むことをお勧めします。

class App : Application() {
    override fun onCreate() {
        super.onCreate()

        val config = SplunkRumBuilder()
            .setApplicationName("rum-session-test")
            .setRealm("us1")  // 実際の値に置き換え
            .setRumAccessToken("YOUR_ACCESS_TOKEN")  // 実際の値に置き換え
            .setDeploymentEnvironment("dev") //実際の値に置き換え
            .build()

        SplunkRum.initialize(config, this)
    }
}

2. WebViewとのセッション統合(最重要ポイント)

ネイティブとWebViewでセッションIDを共有するための設定です。integrateWithBrowserRum()を用いてWebViewインスタンスを計装することで、Mobile RUM SDKが生成したsplunk.rumSessionIdをWebViewのJavaScript環境へ自動注入します。

また、WebView内のBrowser RUM SDKがこのセッションIDを読み取り、同一セッションとして計測します。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val webView = findViewById<WebView>(R.id.webView)
    webView.settings.javaScriptEnabled = true

    // Mobile RUMのセッションIDをWebViewに渡す
    SplunkRum.getInstance().integrateWithBrowserRum(webView)

    // WebView用のHTMLコンテンツを読み込み
    // 冒頭のサンプルアプリでは、下行のように外部URLではなく、簡単なHTMLを直書きしています
    webView.loadDataWithBaseURL("https://example.com", htmlContent, "text/html", "UTF-8", null)
}

3. WebView内でのBrowser RUM初期化

HTML内でBrowser RUM SDKを初期化し、Mobile側と同じ認証情報を使用します。

これは通常のWEBアプリを計装する場合と同様です。

<!DOCTYPE html>
<html>
  <head>
    <script
      src="https://cdn.signalfx.com/o11y-gdi-rum/latest/splunk-otel-web.js"
      crossorigin="anonymous"
    ></script>
    <script>
      SplunkRum.init({
        realm: "us1", // Native側と同じ値
        rumAccessToken: "YOUR_ACCESS_TOKEN", // Native側と同じ値
        applicationName: "rum-session-test", // Native側と同じ値
        deploymentEnvironment: "dev",
      });

      function trackWebClick() {
        // Browser RUM側での手動イベント送信例
        SplunkRum.track("webview.button.clicked", {
          clickCount: ++window.webClickCount,
        });
      }
    </script>
  </head>
  <body>
    <button onclick="trackWebClick()">WebView Click</button>
  </body>
</html>

4. ネイティブ画面の手動計装を実装する

ネイティブ側はAndroidの純正Buttonウィジェットを利用しますが、押下イベントだけでは、トレース情報に出てこないので、RUMイベントを送信するように実装します。

import com.splunk.rum.SplunkRum
import io.opentelemetry.api.common.Attributes
//中略
        val nativeButton = Button(this).apply {
            text = "ネイティブでカウント"
            setOnClickListener {
                nativeClicks += 1
                nativeCounter.text = "ネイティブ画面 - クリック数: $nativeClicks"

                val attributes = Attributes.builder()
                    .put("component", "native")
                    .put("action", "click")
                    .build()
                SplunkRum.getInstance().addRumEvent("native_button_click", attributes)
            }
        }

この実装により、action,componentという属性に加え、クリックイベントをnative_button_clickというイベントとしてバックエンドに送信します。

注意点

  • 認証情報の一致: Mobile RUMとBrowser RUMでrealmrumAccessTokenapplicationNameを必ず同じ値に設定
  • JavaScriptの有効化: webView.settings.javaScriptEnabled = trueが必須
  • セキュリティ: integrateWithBrowserRum()はセッションIDをWebViewへ公開するため、信頼できるコンテンツのみを読み込むこと
  • タイミング: WebViewへのコンテンツ読み込みintegrateWithBrowserRum()を呼び出すこと

この構成により、ユーザーがネイティブ画面とWebViewを行き来しても、Splunk RUMでは1セッションとして課金・集計されます。

セッション統合の確認

Splunk Observability Cloudで実際にサンプルアプリケーションから送信されたトレースを確認します。

下記はUser Sessionsから単一のセッションIDを抽出した画面です。

RUMセッション

ネイティブ側のボタンクリックが、実装された通りnative_button_clickとしてウォーターフォール図に表されているほか、WebViewのクリックイベントもHTML要素と一緒に記録されていることがわかります。

また、それぞれのスパンを確認することで、Attributesに記録されているデータの微妙な差異を確認できます。

ネイティブアプリ側のボタン押下スパン

WebView側のボタン押下スパン

ネイティブ側は機種名やOSバージョン、通信キャリアが取得され、WebView側はブラウザとしてHTML要素やURLを取得していることが分かります。

まとめ

統合処理を行わずにテレメトリーデータを取得した場合、セッションIDが分割されてしまい、ユーザーの正しい行動を追えなくなる可能性があります。また、セッション単位での料金設定や、契約プランごとに上限セッション数がある場合、統合していないことで契約量を余計に消費してしまうことにもなり得ます。

WebViewをもつモバイルアプリを計装する場合、セッションIDの統合処理を忘れず実施しましょう。

Discussion