Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

AppSignalで実現する!Ruby on Railsアプリのカスタム計装とモニタリング徹底ガイド

本記事は全2回のシリーズの後編です。前編では、Ruby on RailsアプリケーションにAppSignalをセットアップして、すぐに使える豊富なインサイトを得る方法をご紹介しました。AppSignalは、エラーの自動追跡、パフォーマンスの監視、一部の依存ライブラリに関するメトリクスのレポートなどを自動的に行ってくれます。

しかし実際には、アプリケーションごとに挙動は異なるもの。汎用的な監視だけでは足りないケースも多々あります。

そこで本記事では、Ruby on Railsアプリケーションにカスタム計装(インストゥルメンテーション)とモニタリングを追加する手順を、サンプルコードとともに解説していきます。これにより、アプリケーションの内部動作をより深く把握できるようになります。

記事のコードを実際に試すための前提条件:

  • www.appsignal.com のアカウント
  • インストール済みで起動中のDocker(docker-composeを使用)

この記事に沿って進めるには、サンプルアプリケーションにご自身のAppSignalアカウントでセットアップを行う必要があります。

カスタム計装とモニタリング

AppSignalが標準で提供する計測以上の情報が必要になったときは、AppSignal gemを使ってRailsアプリケーションにカスタム計装を追加できます。

コードの特定部分を計装する

ここで、新しい機能を追加する場面を想定してみましょう。ユーザーが/postsページですべての投稿を閲覧する際、タイトルが特定の文字で始まる投稿だけを絞り込めるようにします(もちろんもっと複雑な検索でも構いません🪄)。

この新しい検索機能は、すでにPostモデルにPost.where_title_starts_withメソッドとして実装済みです。PostsController#indexを更新して、特定のクエリパラメータが渡されたときにこの新メソッドを使うようにしましょう。

# app/controllers/posts_controller.rb
  def index
    starts_with = params[:starts_with]
    @posts = if starts_with.present?
               Post.where_title_starts_with(starts_with)
             else
               Post.all
             end
  end

これはアプリケーションの中核を担う処理なので、そのパフォーマンスがどうなっているのか、またいつ変化したのかを把握したいところです。AppSignalには、それを実現する手段がいくつか用意されています。

まず、Post.where_title_starts_withメソッドの中身を計装してみます。任意のコードブロックに関するインサイトを得たい場合は、計装ブロックでそのコードを囲みます。メソッドを次のように更新してください。

# app/models/post.rb
def self.where_title_starts_with(letter)
  Appsignal.instrument('Post.where_title_starts_with', "Fetch posts that start with letter") do
    Analytics.track_post_title_search(letter.downcase)
    select('*, pg_sleep(0.01)').where("title ILIKE :letter", letter: "#{letter.downcase}%").load
  end
end

次に、重い処理を行うapp/services/analytics.rb内で呼び出されるAnalytics.track_post_title_searchメソッドも計装します。このケースでは、メソッド全体をより正確に計測できるメソッド計装を使いましょう。

# app/services/analytics.rb
require 'appsignal/integrations/object'
 
class Analytics
  def self.track_post_title_search(letter, sleep = sleep(1))
    # Some heavy processing
    sleep 1
  end
  appsignal_instrument_class_method :track_post_title_search
end

得られるインサイト

上記のコードを保存して数分待ったら、AppSignalダッシュボードで新しい情報が表示されているか確認しましょう(表示されない場合は、Dockerコンテナの再起動が必要なことがあります)。検索パラメータ付きで投稿一覧ページ https://localhost:3000/posts?starts_with=f にアクセスすれば、新機能が正しく動作しているかも確認できます。

データベースに作成された投稿数によっては、/postsエンドポイントのレスポンスがかなり遅くなったことに気づくでしょう。

AppSignalでパフォーマンスの問題を開き('Performance' → 'Issue list')、PostsController#indexアクションを表示すると、ページ下部に「Event Timeline」が現れます。これは、特定のコードの実行にどれだけ時間がかかったかを分解して示してくれるものです。

このタイムラインはすべてのパフォーマンスイベントに存在しますが、ここではカスタム計装イベントも確認できます。たとえばPost.where_title_starts_withの呼び出しには8.84秒かかり、うち2.01秒がAnalytics.track_post_title_searchメソッドで消費され、残りはActive Recordのクエリに使われた、といった具合です。個別のイベント(例:sql.active_record)をクリックすれば、さらに詳しいパフォーマンス情報を調査することも可能です。

AppSignalの計装ヘルパーを活用すれば、アプリケーションコードをより細かく分解して把握できるため、パフォーマンスへの影響が懸念される特定のコード部分について深い洞察を得やすくなります。詳細についてはAppSignalの計装ガイドをご覧ください。

例外処理(エラーハンドリング)

コードのパフォーマンス監視に加えて、アプリケーションが期待どおりに動作しなくなったときに、どこで問題が起きたのかを知ることも重要です。前回、AppSignalが未処理の例外を自動的にレポートする様子を見ましたが、標準機能以上のこともやりたいはずです。

まず、断続的にエラーを引き起こしていた既存のコードを削除しましょう。ダッシュボード上でエラーを確認すると、バックトレースからエラー発生箇所がわかります。app/controllers/pages_controller.rb内のif文を削除してください。

class PagesController < ApplicationController
  def home
    CreateRandomPostsJob.perform_later
  end
end

これにより、概要ダッシュボード上でアプリケーションのエラー率が大幅に低下するはずです。

現在、存在しない投稿をユーザーが閲覧しようとすると、アプリケーションがクラッシュします(例:https://localhost:3000/posts/doesnotexist)。代わりにメッセージを表示したいですよね。そこで、PostsController内の該当箇所にrescueを追加しましょう。#set_postメソッドを次のように更新します。

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
    .
    .
    .
    private
    def set_post
      @post = Post.find(params[:id])
    rescue ActiveRecord::RecordNotFound => e
      render json: { error: "Oops. That post isn't here" } , status: :not_found
    end
    .
    .
end

ただし、例外を手動で処理すると、AppSignalへは自動的にはレポートされなくなります。そんなときは、Appsignal.set_errorを使って手動でエラーをトラッキングできます。

最もシンプルなのは、関数の唯一の引数としてエラーを渡すAppsignal.set_error(e)という書き方です。さらに、リクエストに追加コンテキストを持たせる機能も活用しましょう。AppSignalでは、Appsignal.tag_requestを使って、独自の任意情報でイベントにタグ付けできます。

def set_post
  Appsignal.tag_request(user_id: 'user-from-params', post_id: params[:id])
  @post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
  Appsignal.set_error(e)
  render json: { error: "Oops. That post isn't here" }, status: :not_found
end

この状態で https://localhost:3000/posts/doesnotexist にアクセスし、アプリケーションがクラッシュせず、期待どおりのJSONレスポンスが返ることを確認しましょう。

得られるインサイト

存在しない投稿を閲覧しようとしたあと、追加した修正によってエラーがAppSignalにレポートされるようになっています。AppSignalダッシュボードの「Errors → Issue list」を開き、新しくレポートされたエラー(ActiveRecord::RecordNotFound)を探して詳細を表示してみてください。

エラー詳細ページには、HTTPメソッド、パラメータ、セッションデータといったリクエスト情報がデフォルトで含まれており、エラーの原因を突き止めるのに役立つコンテキストが得られます。さらに、カスタムタグも含まれているのがわかるでしょう。これにより、同じタグを持つエラーをまとめてフィルタリングできます。

リクエストにタグを付けておくと、その情報はエラーだけでなく、他の計装イベントにも付与されます。たとえば個別の投稿ページ(https://localhost:3000/posts/1など)を何度か閲覧したあと、「Performance → Issue list → PostsController#show」でパフォーマンス計測を見ると、そこにもタグが表示されていることに気づくでしょう。トランザクションへのタグ付けについては、ガイドで詳しく読めます。

このようにトランザクションにカスタムメタデータを追加できることは、本番環境での問題診断に大きな可能性をもたらします。たとえばKubernetesのメタデータをエラーに付与するのも、その優れた活用例のひとつです。

メトリクス

ここまででカスタム計装とエラー監視が整いました。ここでふと気づいたとしましょう——投稿検索に大きな急増(スパイク)が発生することがある、と。ユーザーが検索するたびにAnalytics#track_post_title_searchが呼ばれ、計算処理とサードパーティサービスへのAPI呼び出しが行われます。このサードパーティAPIにはレート制限があるため、呼び出し頻度を追跡して、制限にどれほど近づいているかを常に把握しておきたいところです。

AppSignalなら、アプリケーション全体で自由にカスタムメトリクスを追跡できます。

まず、カウンターとタグを使って、アナリティクスサービスをどのくらいの頻度で、どんなデータとともに呼び出しているのかを追跡します。

#app/services/analytics.rb
require 'appsignal/integrations/object'
 
class Analytics
  def self.track_post_title_search(letter, sleep = sleep(1))
    Appsignal.increment_counter("track_post_search", 1, { letter: letter })
    # Some heavy processing
    sleep 1
  end
  appsignal_instrument_class_method :track_post_title_search
end

次に、PostsController#indexで返される投稿数も追跡します。これはアプリケーションの挙動の中核であり、しかも継続的に増え続けていることがわかっているからです。

#app/controllers/posts_controller.rb
class PostsController < ApplicationController
    .
    .
  def index
    .
        .
    Appsignal.set_gauge("posts_index", @posts.size, starts_with: params[:starts_with])
  end
end

アプリケーション上で動き続けている疑似トラフィックスクリプトもある程度データを生成してくれますが、バリエーションを増やすために、f、l、vで始まる投稿の検索も手動で実行しておきましょう。

得られるインサイト

カスタムメトリクスを表示するには、AppSignalでカスタムグラフを備えたダッシュボードを作成する必要があります。UIから作成してもよいですが、ここでは既製のダッシュボードをインポートしてみましょう。「Dashboard」セクションの「Add dashboard」をクリックし、以下の内容でダッシュボードをインポートします。

{
  "title": "Post Search",
  "description": "Sample dashboard about posts search activity",
  "visuals": [
    {
      "title": "Analytics",
      "line_label": "%name% %letter%",
      "display": "LINE",
      "format": "number",
      "draw_null_as_zero": true,
      "metrics": [
        {
          "name": "track_post_search",
          "fields": [
            {
              "field": "COUNTER"
            }
          ],
          "tags": [
            {
              "key": "letter",
              "value": "*"
            }
          ]
        }
      ],
      "type": "timeseries"
    },
    {
      "title": "Search",
      "line_label": "%name% %starts_with%",
      "display": "LINE",
      "format": "number",
      "draw_null_as_zero": true,
      "metrics": [
        {
          "name": "posts_index",
          "fields": [
            {
              "field": "GAUGE"
            }
          ],
          "tags": [
            {
              "key": "starts_with",
              "value": "*"
            }
          ]
        }
      ],
      "type": "timeseries"
    }
  ]
}

数分以内にグラフ上にデータが表示されるはずです。線にマウスオーバーすると、表示中の時間帯に収集されたメトリクスの凡例が確認できます。

注目すべきは、タグ値ごとに異なる線が描かれる点です。現在、疑似トラフィックは文字eのみを検索していますが、先ほど手動で他の文字でも検索したため、それぞれのデータポイントが新しい線としてグラフに現れます。

これで十分だと思いましたか? 実はAppSignalには、ここでは扱いきれなかったカスタム計装ソリューションがまだあります。ひとつ簡単に触れておきたいのがブレッドクラム(breadcrumbs)です。ブレッドクラムを使うと、アプリケーション内の一連のアクションを追跡でき、その履歴がエラーレポートに添付されます。これにより、エラーに至るまでの経緯を、より具体的かつ時系列順に把握できるようになります。

カスタム計装については、公式ガイドでぜひすべて目を通してみてください。

まとめ:AppSignalによるRubyアプリのカスタム計装とモニタリング

シリーズ前編では、Rubyアプリケーション向けのAppSignalの基本的なセットアップと使い方を取り上げました。

そして今回の後編では、標準のモニタリングだけでも十分に優れたアプリケーションを、AppSignal gemによってさらに強化しました。

AppSignalのカスタム計装、エラートラッキング、パフォーマンス監視機能は、アプリケーションの挙動を理解するために必要な洞察を与えてくれます。標準機能だけでも多くの価値を提供しながら、必要な場面では細部までコントロールできる——まさに理想的な監視ツールといえるでしょう。

さあ、あなたのコードを自由に世の中へ放り出しましょう。あとは、その調子をしっかり見守るだけです。それでは、Happy coding!

P.S. Ruby Magicの最新記事をいち早く読みたい方は、Ruby Magicニュースレターを購読してください。記事を見逃すことはもうありません!

  1. Rubyでコマンドラインアプリケーション(CLI)を作る方法を徹底解説

    RubyはWebアプリケーション開発のイメージが強く、それ以外の用途があることを忘れられがちです。そこで本記事では、Rubyを使ってコマンドラインアプリケーション(CLI)を構築する方法を詳しく解説します。 まず、皆さんがよく目にするコマンドラインアプリケーションの例を挙げてみましょう。 psql(PostgreSQLの対話型ターミナル) rails bundler gem git CLIアプリケーションの作り方はさまざまですが、この記事では代表的な3つの方法に焦点を当てて紹介します。 この記事で学べること ARGV配列の使い方 OptParseライブラリの使い方 Thor gemの使い

  2. Oculus GoでVR体験を次のレベルへ!知っておきたい便利機能と活用テクニック

    Oculus Goは、単体で完結するスタンドアロン型VRヘッドセットです。スマートフォンもPCも接続不要で、体に絡みつく外部ケーブルの煩わしさもありません。効果的な内蔵スピーカーと鮮明なディスプレイを備え、これまでに発売されたVRシステムの中で最も手軽に使える一台と言えるでしょう。 市場に出ているVRヘッドセットの中でも、携帯性が高く価格も手頃でありながら、価格以上の体験を提供する優秀なモデルです。Amazonでは199ドルという妥当な価格で販売されており、世界中のユーザーから5点満点中4.5という高評価を獲得しています。 この素晴らしいテクノロジーの逸品を購入しようと考えているなら、その有