Timee Product Team Blog

タイミー開発者ブログ

モノリスの運用課題を解決するためにコードオーナーをSentryとDatadogに送る

モノリス特有の運用課題

こんにちは。バックエンドエンジニアの須貝です。

タイミーのバックエンドAPIはモノリスなRuby on Railsアプリケーションです。2024年12月現在、このリポジトリ上で10程度のチームが開発しています。

モノリスは利点も多いのですが、チームが増加するにつれて運用面でモノリス特有の難しさを感じることも増えてきました。例えば、SentryやDatadogで何かエラーや問題を検知しても「これはどこのチームの持ち物なのか」という責任があいまいになってしまい改善がなかなか進まない、基盤的なチームがエラーのトリアージをするにしても調査の負担が大きい、といった課題がありました。

SentryとDatadogにコードオーナーを送る

「まずどこのチームの持ち物なのかわかりやすくしよう」ということで、SentryとDatadogにコードオーナー(リポジトリ内の特定のファイルやディレクトリに責任を持つチーム)の情報を送信するようにしました。すでにバックエンドのリポジトリではGitHubのCODEOWNERSを導入していたのでこちらを利用しました。

なお、CODEOWNERSファイルのパースと、ファイル・クラスなどからコードオーナーを取得する処理ではCodeOwnershipというgemを活用しています。 github.com

やることはシンプルでSentryとDatadogに情報を送信する際に、CodeOwnership gemを使ってコードオーナー名を取得し、tagのような形で付与してあげるだけです。以下、それぞれの実装例を紹介します。

Sentry編

まず、Sentryにコードオーナーを送信するところから見ていきましょう。

CodeOwnershipはbacktraceを渡してコードオーナーを返すこともできるので、Sentryにコードオーナー情報を送信する実装も簡潔です。以下の実装例のようにsentry-ruby gemの設定に数行追加するだけで済みます。

Sentry.init do |config|
  # ...諸々省略
  config.before_send = ->(event, hint) do
    if hint[:exception]
      # コードオーナー名を取得
      code_owner = CodeOwnership.for_backtrace(hint[:exception].backtrace)&.name || 'unknown'
      # code_ownerというtagでSentryに情報を送信する
      event.tags[:code_owner] = code_owner
    end
  end
end

CodeOwnership.for_backtraceは渡したbacktraceを先頭から見ていき、コードオーナーが見つかった場合はそのコードオーナーを返す便利なメソッドです。

これでSentry側にtagとしてコードオーナー情報を送信するようになったので、Sentryの管理画面上でも確認できるようになっています。下記の画像のようにIssueの画面のTagsにコードオーナー名(この例ではWorking Relationsというチーム)が表示されています。

Sentryの画面で見た様子

さらに便利にするためにSentryから飛んでくるSlack通知でもコードオーナー情報を見られるようにしました。SentryのAlertルールの編集で「Set conditions」の「THEN」の「and show tags」の箇所にcode_owner(上記のコード例で設定したtag名)を追加するだけです。

SentryのAlertの編集画面

これでSlack通知で下記のようにコードオーナー名(この例ではIronBankというチーム)が見られるようになりました。

SentryからのSlack通知

Datadog編

続いてDatadogのAPMでもコードオーナー情報を見られるようにしていきます。Controller(APIリクエスト)とSidekiqのjob(非同期処理)の2パターンに対応します。

まずControllerです。下記のようなModuleを書いてControllerでincludeします。

module DatadogTaggable
  extend ActiveSupport::Concern

  included do
    before_action :set_datadog_tags
  end

  private

  def set_datadog_tags
    span = Datadog::Tracing.active_span
    return unless span

    code_owner = CodeOwnership.for_class(self.class)&.name || 'unknown'
    span.set_tag('code_owner', code_owner)
  rescue StandardError => e
    Rails.logger.warn("Failed to set code_owner tag to span: #{e.message} (#{e.class})")
  end
end

すると下記のようにDatadogのAPMでコードオーナー名(この例ではWork Wellというチーム)が確認できるようになります。

Datadog APMのTrace

次はSidekiqです。下記のようなMiddlewareを書いて上記のModule同様にDatadogのspanにtagをセットしてあげましょう。

class SidekiqDatadogMiddleware
  include Sidekiq::ServerMiddleware # loggerを使用するため

  def call(worker, _job, _queue)
    span = Datadog::Tracing.active_span
    if span
      code_owner = CodeOwnership.for_class(worker.class)&.name || 'unknown'
      span.set_tag('code_owner', code_owner)
    end
  rescue StandardError => e
    logger.warn("Failed to set code_owner tag to span: #{e.message} (#{e.class})")
  ensure
    yield
  end
end

あとはSidekiqの設定で上記のMiddlewareを追加してあげれば完了です。

Sidekiq.configure_server do |config|
  # ...略
  config.server_middleware do |chain|
    chain.add SidekiqDatadogMiddleware
  end
# ...略
end

おわりに

実際に上記の仕組みを導入してみて、自分がSentryのエラー通知をよく見ているというのもあり、ぱっと見で担当チームがわかるのはだいぶ助かるなあと日々感じています。主要なエンドポイントやJobは自分の中に脳内マッピングができている(気がする)ものの、さすがに限界を感じ始めていたのでこれ無しではもう生きていけません。後はSentryは最終的にはSlackでメンションまでできるようにしたいのですけど、あまり賢い方法が思いつかずに困っているので良い案のある方は教えてください。

また、上記では紹介しきれなかったのですが、ログにもコードオーナー名を付与しているのでDatadog Logsでもコードオーナーを見られるようにしています。コードオーナーを条件に入れてアラートを設定したり、ダッシュボードを作ることも可能になっているので、チーム単位でのオブザーバビリティの向上にも寄与するのではないかと考えています。

最後に自分が所属しているRailway(Rails WayではなくRailway)というチームではこんな感じでバックエンドアプリケーションの横断的な課題を解いたり、他のバックエンドエンジニアの開発生産性を上げたりするような活動を行っています。なんと、いま絶賛一緒に働いてくれる方を探しているので、カジュアル面談でお話しましょう。

product-recruit.timee.co.jp