Stockmark Tech Blog

自然言語処理テクノロジーで社会を進化させる ストックマークのテックブログです。

Railsログの検索性を高めるための rails_semantic_logger 導入記録

1. はじめに

Aconnectのバックエンドでは、Datadogを活用しアプリケーションのモニタリングを行っています。しかし実際の運用の中で「調査がしにくい」「見たい情報がすぐに見つからない」といった課題がたびたび発生していました。

これは、ログ出力の形式や内容が、Datadogの機能を活かしきれていなかったことに起因します。そこで我々は、ログの構造化と内容の見直しに取り組み、Datadog上での可視性と分析性の向上を目指しました。

この記事では、Railsバックエンドの構造化ログへの移行と、その第一歩として導入した rails_semantic_logger を中心に、導入の背景・ステップ・得られた効果についてお話しします。

2. 課題

Aconnectでは、アプリケーションログをDatadog Logsで検索できるようにしています。しかし、ログの内容が不十分だと、どれだけ高機能なツールを使っていても、問い合わせや障害調査の助けにはなりません。

これまでAconnectでは、ログを構造化せず、Railsのデフォルト出力形式のままDatadogに連携していました。そのため情報量の不足に加えて、構造化されていないことが原因で、問い合わせや調査、特に特定のユーザーや環境に起因する問題調査が難しい状態になっていました。以下は、これまでDatadogに連携していたログの一例です。

I, [2025-07-23T08:25:13.000000 #10]  INFO -- : [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx] Started GET "/aconnect/v1/articles/xxxxxxxxx" for 192.168.1.1 at 2025-07-23 08:25:13 +0000
I, [2025-07-23T08:25:13.000000 #10]  INFO -- : [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx] start suggesting related articles_id: xxxxxxxxx

Datadog側のPipeline設定である程度の補正は可能ですが、ログの情報をさらに充実させようとすると、Pipelineを複雑化させるか、もしくはそのままテキストとして流すしかなくなります。前者は運用負荷が増えますし、後者は検索性が損なわれるという問題がありました。

このような状況を改善するため、まずはログの構造化を行い、そのうえで内容の拡充を図ることにしました。本記事では、その中核となった rails_semantic_logger (https://logger.rocketjob.io/rails.html) の導入プロセスと、その成果についてお話しします。

3. なぜ rails_semantic_logger だったのか

前述の通り、AconnectではすでにDatadogによるログ収集とモニタリングを行っています。AconnectのバックエンドはECS上で稼働しており、アプリケーションログはFireLensドライバを経由して log_router コンテナ(Fluent Bit)に転送されます。そこでJSON解析し、Datadogに送信される構成になっています。

このように、ログ転送基盤としてはJSON形式に対応しているにもかかわらず、アプリケーション側の出力は依然としてプレーンテキストのままでした。

複数の選択肢を検討した結果、rails_semantic_logger は以下の理由から最適と判断しました。

  • Railsとの統合が容易で、導入コストが低い
  • 標準的な構造化ログの形式に従っており、将来的なメンテナンス性が高い
  • すでにJSON形式に対応した基盤が整っていた

4. どのように適用したか

まずはdefault formatのまま開発環境へ

ログ形式の変更に伴いDatadog側のモニタリング設定に影響が出る可能性があるため、まずは rails_semantic_logger のデフォルト形式で開発環境へ適用しました。

ローカルのみでの検証も可能でしたが、実際に backend → log_router → Datadog Logs の連携経路に沿って、各設定の挙動を確認し、ログ周辺の仕組みに対する理解を深めたかったので、ワンクッションこの工程を挟むことにしました。

具体的には以下のような設定を追加しました。

  • Gemfile
group :development do # development環境のみに適用
  gem 'rails_semantic_logger'
end
  • config/development.rb
config.rails_semantic_logger.semantic   = true
config.rails_semantic_logger.started    = true
config.rails_semantic_logger.processing = true
config.rails_semantic_logger.rendered   = true

PumaをCluster modeで使っている場合(workerを設定している場合)は、以下の設定がないとログが出力されませんので要注意です。

  • config/puma.rb
if Rails.env.development? # development環境でしか有効にしないため。全環境に適用する場合は不要です。
  on_worker_boot do
    SemanticLogger.reopen
  end
end

これら対応を開発環境へ適用しました。そのうえで、Datadog側に不要にみえるPipeline設定が多数存在していたので、本当に削除してよいか確認しながら、Indexingの結果やモニタリング設定への影響を検証しました。

JSON形式へ変更

既存のモニタリング設定に影響ないことが確認できたので、JSON形式で連携することにしました。また不要と判断したPipeline設定も整理し、構成もシンプルな形に変更しました。

以下はJSON形式での連携する場合の設定例です。

  • config/development.rb
config.rails_semantic_logger.format     = :json
config.rails_semantic_logger.semantic   = true
config.rails_semantic_logger.started    = true
config.rails_semantic_logger.processing = true
config.rails_semantic_logger.rendered   = true

この変更により、Datadog Logs上から構造化されたログが、Indexingされていることを確認できました。

本番適用とタグ追加

開発環境で問題なく動作することが確認できたので、rails_semantic_loggerを全環境へ適用しました。

今回は検索性の向上に加えて、ログ内容の拡充も目指していたので、user_idなどの情報をカスタムペイロードとして常に追加するように対応しました。

Action Controllerであればドキュメントにある方法で簡単に実現できるのですが、我々はGrape (https://github.com/ruby-grape/grape) を使っていることもあり、Rackミドルウェアを作成し、その中でSemanticLogger.named_taggedを使いタグを付与するように対応しました。

以下はそのコードのサンプルです。実際には他のタグも付与していますが、ここではわかりやすくuser_idのみに絞っています。

  • config/initializers/tagging_user_info_middleware.rb
class TaggingUserInfoMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    req = ActionDispatch::Request.new(env)
    res = nil

    # リクエストのユーザー情報を取得する
    user_info = get_user_info(req)

    user_id = user_info['user_id'].presence || 'no_login'
    # これでログにタグ追加できます。
    SemanticLogger.named_tagged({ aconnect_user: { user_id: } }) do
      res = @app.call(env)
    end
  rescue StandardError => e
    # 例外が発生した場合でも処理を止めないようにする
    # なおGrapeでは例外が発生しても500番のエラー系処理として扱われるので、この異常系処理に来ることはない
    Rails.logger.warn "ユーザ情報をログに出力するときにエラーが発生しました。#{e.message}"
    # ここで例外が出たときは異常系の終了となる
    @app.call(env) if res.nil?
  end
end

# SemanticsLoggerを使っておりかつ、sematicがtrueの時だけ読み込む
# それ以外は RailsSemanticLogger::Rack::Logger が呼ばれないのでエラーになる
if Rails.logger.class == SemanticLogger::Logger && Rails.configuration.rails_semantic_logger.semantic
  Rails.configuration.middleware.insert_before(RailsSemanticLogger::Rack::Logger, TaggingUserInfoMiddleware)
end

この対応により、バックエンドログには必ずuser_idが付与されるようになり、特定ユーザーの調査などが格段にしやすくなりました。

5. 結果 : ログ検索で分析が可能に

構造化ログの導入とタグ追加ににより、Datadog上でのログ検索や調査が格段にしやすくなりました。

以下は特定のuser_idで絞り込んだ時のDatadog Logsの検索結果のキャプチャです。

user_idで絞り込んだ時のDatadog Logsの検索結果
この時は、「特定の機能がエラーで使えない」というお客様からの問い合わせで、user_id でログを絞り込むことで、以下のような調査が可能になりました。

  • 本当にその機能が使えない状態なのか?
  • 一部のリクエストだけなのか、それとも完全に利用できないのか?
  • 問題が起きる前後に、どのようなリクエストがあったのか?

また、対象機能に至るまでの一連のアクセスログを時系列でたどることも用意になり、問題の切り分けがスムーズに行えるようになりました。DatadogのUIではログのリストに表示するカラムを任意にカスタマイズできるため、調査や分析の目的に応じて見やすい形式でログを確認できるようになった点も大きなメリットです。

さらに、AconnectではDatadog APMも利用しているので、ログとAPMを行き来しながら、より深掘って調査、分析が行えるようになりました。

6. まとめ

これまではエラーメッセージに対してピンポイントでヒットするように検索していたので、ある種職人芸っぽくなっていました。今回の構造化対応により、そういった属人的な運用が改善されました。Datadogのような強力なモニタリングツールを導入していても、ログの出力形式や構造が整っていなければ、十分な価値を引き出すことはできません。

今回 rails_semantic_logger を導入し、ログを構造化することで、以下のような効果を得ることができました。

  • ログの検索性・可視性の向上
  • Datadog UIでの表示・分析が容易に
  • APM連携による調査・分析フローの効率化

特に、ログに user_id などのコンテキスト情報を付与したことは、大きな効果があったと言えます。

ログの改善は継続的な運用改善の一環です。今後も、小さな改善を積み重ねていき、より信頼性の高いサービス運用を目指していきます。

最後に、Stockmarkでは一緒にプロダクトと組織を成長させていただける方を広く募集しています! カジュアル面談からお気軽にご連絡ください。

参考

最後まで読んでいただきありがとうございました!

本記事は、ストックマークでプロダクトエンジニアをしている酒井がお届けしました。