問題
アプリケーションにnewrelic_rpm
gem を追加した後、またはバージョン 7.0.0 以降にアップグレードした後、 stack level too deep (SystemStackError)
エラーが表示されます。
解決
ほとんどの場合、Ruby エージェントは根本的な問題ではありません。問題は、メソッド チェーン (alias_method) と Module#prepend が特定の状況でのみ連携することです。正しく一緒に使用しないと、終了しない再帰が発生する可能性があります。Ruby エージェントは、Module#prepend とメソッド チェーンの gem インストルメンテーションの両方を提供して、お客様に柔軟性を提供します。メソッド チェーンと Module#prepend の非互換性の詳細については、以下を参照してください: New Relic Ruby エージェントが関与するModule#prepend
およびalias_method
の競合の解決
1.フルスタックトレースの取得
最初に行う必要があるのは、表示されているエラーの完全なスタック トレースを取得することです。完全なスタック トレースが必要な理由は、再帰を示すスタック トレース内のセクションを見つけて、競合している 2 つの場所を見つける必要があるためです。Rails のログや Passenger のログなどは、スタック トレースが非常に長いため、しばしば切り捨てられるため、切り捨てられていない完全なスタック トレースを取得できる環境でエラーを再現する必要がある場合があります。これを行う 1 つの方法は、 stack level too deep
例外で Exception#backtrace を呼び出すことです。これにより、完全なスタック トレースが返されます。
2.スタックトレースから矛盾するコードを見つける
完全なスタック トレースを取得したら、1 つの gem とnewrelic_rpm
gem の間で繰り返しのあるセクションを探します。これはおそらく競合を引き起こしている宝石です。Module#prepend およびメソッド チェーンと競合する場所を示すスタック トレースのセクションの例を次に示します。
/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rails/net_http.rb:11:in `block in request'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rack.rb:25:in `capture_timing'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rails/net_http.rb:10:in `request'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/chain.rb:16:in `block in request_with_newrelic_trace'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb:26:in `block (2 levels) in request_with_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/tracer.rb:371:in `capture_segment_error'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb:25:in `block in request_with_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent.rb:501:in `disable_all_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb:24:in `request_with_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/chain.rb:16:in `request_with_newrelic_trace'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rails/net_http.rb:11:in `block in request'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rack.rb:25:in `capture_timing'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rails/net_http.rb:10:in `request'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/chain.rb:16:in `block in request_with_newrelic_trace'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb:26:in `block (2 levels) in request_with_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/tracer.rb:371:in `capture_segment_error'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb:25:in `block in request_with_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent.rb:501:in `disable_all_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb:24:in `request_with_tracing'/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/chain.rb:16:in `request_with_newrelic_trace'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rails/net_http.rb:11:in `block in request'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rack.rb:25:in `capture_timing'/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rails/net_http.rb:10:in `request'
どのファイルが衝突の原因になっているか、行番号も含めて確認できます。これにより、どのgem instrumentationがこのエラーの一部であるかを知ることができます。上の例を使うと、再帰を引き起こしているのは2箇所。
/Users/user/.rvm/gems/ruby-2.6.5/gems/newrelic_rpm-7.0.0/lib/new_relic/agent/instrumentation/net_http/chain.rb:16:in `request_with_newrelic_trace'
/Users/user/.rvm/gems/ruby-2.6.5/gems/airbrake-11.0.1/lib/airbrake/rails/net_http.rb:11:in `block in request'
この2箇所のコードを見てみると、一方はprependを使い、もう一方はmethod chainingを使っていることがわかります。この例では、エージェントはmethod chainingを使用し、AirbrakeはModule#prependを使用しています。エージェントはmethod chainingとModule#prependの両方を提供しているので、競合するgemと互換性のある戦略を使用するようにエージェントを設定することができます。
3.設定ファイルの更新
スタックトレースのエージェントからの行をよく見てみると、パス名の中にどのようなgem instrumentationを設定する必要があるかがわかります。
new_relic/agent/instrumentation/net_http/chain.rb:16
ファイル パスのnew_relic/agent/instrumentation/
の後に gem 名が続きます。この例では、これが Net::HTTP インストルメンテーションであることがわかります。このインストルメンテーションを制御するには、構成ファイルでnet_http
を使用する必要があります。ファイル名を見ると、これがメソッド チェーン インストルメンテーションを使用していることもわかります。メソッド チェーンのファイル名は/chain.rb
で、Module#prepend の場合は/prepend.rb
になります。
競合の一部であることがわかった gem インストルメンテーションを使用して、構成オプションをnewrelic.yml
に追加します。この例では、メソッド チェーン インストルメンテーションとの競合が原因でエラーが発生しました。これを解決するには、Net::HTTP インストルメンテーションで Module#prepend を使用するように設定します。
instrumentation: net_http: prepend
代わりに、 prepend.rb
ファイルがスタック トレースで参照されていることがわかった場合は、代わりにこの構成オプションをchain
に設定します。
エージェントの利用可能な設定オプションの詳細については、当社の 設定資料 の instrumentation セクションを参照してください。
4.まだエラーが出ますか?
上記の手順を実行してもスタックレベルが深すぎるというエラーが表示される場合は、同じコンフリクトとスタックトレースであるか、あるいは先ほど修正したコンフリクトとは別のコンフリクトであるかを確認してください。
別のスタックトレースで別のコンフリクトが発生した場合は、新しいスタックトレースで上記の手順を繰り返します。これにより、新たに浮上したコンフリクトが解消されるはずです。
スタックトレースが最初のものと同じであれば、エージェントが設定ファイルをロードできるかどうかを確認します。エージェントが設定ファイルの場所や読み込みに問題がある場合、その問題を解決することで、エージェントが必要なgem instrumentationを使用できるようになるはずです。詳細については、 設定ドキュメント を参照してください。
原因
アプリケーション (またはアプリケーションで使用される gem) のある場所で、別の場所 (または gem) が後で alias_method を使用するメソッドで Module#prepend を使用すると、終了しない再帰が作成され、 SystemStackError: stack level too deep error
がスローされます。
Ruby エージェントの 7.0 リリースでは、これまでメソッドチェイニングのみを使用していたすべての gem のインストゥルメンテーションに Module#prepend インストゥルメンテーションが追加されました。また、ほとんどの場合、Module#prepend がデフォルトで使用されます。エージェントでは、gem インスツルメンテーションにメソッドチェイニングを使用することができます。この動作は、 エージェント設定 で制御されます。
エージェントがgem instrumentationの設定にデフォルト値を使用すると、Module#prependとの一般的な既知のコンフリクトについて環境をチェックします。この衝突の原因となるgemが検出された場合、エージェントは代わりにメソッド・チェイニング・インストゥルメンテーションをインストールします。
しかし、考えられるすべてのコンフリクトを把握しているわけではありません。どんなgemやアプリケーションでも、任意のメソッドにメソッド・チェイニングを追加することができます。これが、どのタイプのインスツルメンテーションを使用するかを設定するオプションを提供している理由です。
以下は、エージェントがチェックし、互換性のあるgem計測器をインストールする既知のコンフリクトの例です。
Net::HTTP Instrumentation
Airbrake< 10.0.2 が使用されている場合、Net::HTTP メソッドでメソッドチェーンを使用します。Airbrake 10.0.2+ では、Net::HTTP で Module#prepend を使用するように更新されたため、そのバージョン以上が検出されると、エージェントは prepend instrumentation をインストールします。
Resque Instrumentation
Airbrake< 11.0.3 が使用されている場合、Reque メソッドでメソッドチェーンを使用します。Airbrake 11.0.3+では、ResqueでModule#prependを使用するように更新されたため、そのバージョン以上が検出されると、エージェントはprepend instrumentationをインストールします。
Redis Instrumentation
PrometheusExporterが検出されたときにメソッド・チェイニングを使用します。このgemはredisメソッドにメソッド・チェイニングを使用するからです。