はじめに
本田技研工業のRoadSyncチームのバックエンドを担当している中村です。
外部への発表を積極的にやっていこう、という趣旨でTechブログの投稿第一弾になります。
TL;DR
- デプロイレスでログの流量制御ができるようになってるよ。
- UserIDやAPI、リクエストの処理時間などでログ出力の可否を決めているよ。
- ログの出力を決定する定義はGCS上にJSONで保存して、定期的に読み込んでいるよ
RoadSyncとは
バイクを運転中にハンドルスイッチとボイスコントロールでメッセージや音楽、ナビゲーションなどを安全に操作することのできるアプリです。
Hondaではこのアプリの内製化に取り組んでいます。
バックエンドの課題
バックエンドの全体像
バックエンドチームはGCP上で(そこそこイケてる)インフラを構築しています。
Firebaseで認証したリクエストをCloudRunで受け取り、DBとBigQueryへ流し込んでいるのが大まかな流れになります。
また、開発体制としてDev,Stag,Prodの3段構成をしており、
- Dev = バックエンドの開発用
- Stag = クライアント及びQAの検証用
- Prod = 本番
として運用していました。
以下、ざっくりとしたシステム構成。よくあるGateway、サービス、DBの3段構成です。(実際はもう少し複雑ですが)
ログの利用状況
ログが活用できそうな場面としては以下の業務が想定されます。
業務 | 要望 |
---|---|
エラー監視 | ERROR, WARNレベルはマストで見たい。 でも、INFOレベルは場合によっては見たい。(直近でリリースしたAPIなど) |
バックエンドの機能開発と運用改善 | 全部見たい |
クライアントのAPIを利用する機能の確認 | Prodで検証する場合、テストアカウントだけ全部のログを見たい |
Stagで検証する場合、基本的に全部見たい | |
BigQueryをベースにした分析基盤の提供 | バッチ処理のステータスや結果はINFOログに出しておきたい |
カスタマーサポートから上がってくる不具合の調査 | カスタマーサポートへコンプレインを寄せていただいたユーザのログを追跡し、状況の再現などを監視したい |
従来はログのON/OFFの切り替えのために環境変数をフラグにしたFeature Flag的な運用をしていましたが、以下のような欠点があります。
- 一つのログの切り替えにデプロイが発生する
- Weeklyのリリースを行っている中で、Prodへの反映はどうしても遅れる
- Dev,Stag,Prodが今どの出力制御になっているか把握がしんどい
また、CloudRunのログ形式にフォーマットされておらず
- メタデータが構造化されておらず、ログの種類によって表記がまちまちだった
- ErrorReportingっていう超便利なエラー管理サービスでコントロールできていなかった
という弱点も同時に抱えておりました。
Logger
Overview
そこで、Ruled Loggerを紹介します。以下を満たした実装で、NestJS上で動作しています。
- 定期的にGCS上に保存している
rules.json
を見に行く - エラーのレベルはERROR/WARN/INFOの3つ
- ERRORとWARNは常に出力、INFOは
Rule
に応じて出力を決定 - ログの呼び出しにはメタ情報(UserIdやApiNameなど)を付与する必要があり、構造化された形式でLoggingされる
- INFOが呼び出された際、
rules.json
をパースし理解した簡易的なルールエンジンが動いており、メタ情報とrules.json
を照らし合わせて出力するかを決定します。
どんなことができるのか
Ruledな機能
- 特定のAPI/ユーザ/アプリバージョン/プラットフォームだけ、ログを出力 - - - 条件とする
- 条件にマッチしたログで、レスポンスまでの処理に一定時間以上掛かった場合のみ出力
- 条件にマッチしたログを一定の確率で出力
構造化機能
- Cloud Loggingの画面で、特定の条件を満たすログだけをフィルタリングできるようなった
- TraceIdが付与されて、リクエスト単位でログを辿れるようになった
- デプロイフローとは別の経路でログを出力できるようになった
- ERRORはErrorReportingに記録されるようになり、エラーのトリアージが可能になった
できたもの
ルールは以下のような定義ファイルを参照する。
カスタマーサポートから打ち上げがあると、ログを有効にしてアプリのサービス向上に役立てたり
リリース直後のAPIはとりあえずログを吐き出すようにしたり、何かと使いみちがあると思います
{
"$schema": "./schema.json",
"description": "enable logging at Trips",
"userId": ["xxxxxxxxxxx"],
"platform": ["android"],
"samplingRate": 1,
"apiName": [
"GetTripDetail",
"ListTrips",
"DeleteAllTrips",
],
"options": {
"timeOption": true
}
}
日々のログ管理画面はメタ情報が付与され、出力がコントロールされ
GCPの神サービスの一つErrorReportingにも集約されます
結論
- ログは丁寧に構造化しよう。
- デプロイで変更する機能とデプロイ外で変更する機能を分離しよう。