こんにちは、R&Dチームの中村健太郎です。
Mask R-CNNでいちごを検出する話、3DCGモデルからデータセットを作る記事を過去に投稿しましたが、今回はガラリと変わってiOSアプリの話で記事を書きました。余談ですがiOSアプリを作る手段はReactNativeだったり、Swiftでゴリゴリだったりしますが、実際のところ今どのくらいの割合でそれらが採用されているのかが気になるこの頃です。
はじめに
クライアントアプリにおけるイベントログ収集のモチベーション
どれだけ気をつけて開発をしていても、リリース後のバグや予期しない挙動は発生してしまいます。
サーバーサイドの場合、サーバーで発生した問題・サーバー起因の問題はサーバーログを頼りに原因の究明と改善を進めていくと思います。 しかしクライアントアプリの問題(ネットワークが絡まない問題)の場合は、ログを取っていたとしてもログファイルを受け取る窓口を別途用意する手間や、ログファイルやり取り自体の手間もかかってしまいます。
またクライアントアプリならではの特徴として、ユーザー(人)を介してしまうとリテラシーによって得られる発生状況や環境情報がバラバラ、かつ本当にその情報と実態が一致しているかの確認にも時間がかかってしまいます。これは仕方のないことです。
イベントログの収集はクライアントアプリにおいて、リリース後の問題に関する情報を素早く・正確にキャッチする重要な役割になると思います。
Sentry
タイトルにもある通り今回はSentryを使って収集してみます。使ってみた主観的な点も含めた特徴を3つ上げると以下のようになりました。
- SaaSでもオンプレでも使うことができる
- クロスプラットフォーム対応で主要どころはおさえられている
- 言語単位(Go, Swift, Java, Python, Javascript など)に加えてポピュラーなライブラリ単位(net/http, Flask, Vue.js, React, Laravel など)でもプロジェクトが用意されている
- 導入が簡単(主観的)
今回はiOSアプリ(Swift)にSentry SDKを導入して、実際にイベントログが収集できているところまでを確認してみたいと思います。
Sentry iOS SDKを導入してみる
Sentryプロジェクトの作成
Sentryへのサインアップは完了している前提で進めます。
Organization name はsentry-example
、User nameはkentaro.nakamura
としていますので適宜読み替えてください。
プロジェクト一覧よりCreateProjectボタンをクリックします。
次にプロジェクト一覧からSwiftを選択して、CreateProjectボタンをクリックします。今回プロジェクト名は省略しているので、デフォルトのswift
になっています。
すると、クライアントアプリ用SDK(sentry-cocoa*1)のインストール方法の画面に飛びます。実はもう既にWebからやる必要のある最低限の作業は終わっています。
sentry-cocoaの導入
Sentry Docs: Installationに記載されているため省略します。私はCarthage*2を使いました。 バージョン4.4.3以降はSwift Package Managerも対応しています。
iOSアプリでの設定
今回はXcode上でSingle View Appのプロジェクトを作成して試してみます。
Sentry Docs: Configurationに記載の通り、AppDelegate内でインスタンスを生成します。
// AppDelegate.swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { do { Client.shared = try Client(dsn: "https://xxxxxxxxxxxxxxxxx@sentry.io/xxxxxxx") try Client.shared?.startCrashHandler() } catch let error { print("\(error)") } return true }
このときdsnに指定したアドレスはhttps://[CLIENT KEY]@sentry.io/[PROJECT ID]
の形式になっており、https://sentry.io/settings/[YOUR ORGANIZATION NAME]/projects/[YOUR PROJECT NAME]/keys/
へアクセスするとアドレスとクライアントキーの確認・再生成・失効をすることができます。
Errorイベントを送信してみる
ボタンを1つStoryboard上で作成・配置して、ボタンを押したタイミングでErrorイベントを送信するようにしてみます。
以下のようにボタンを配置して、ViewControllerと紐付けます。ボタンをタップしたときにレベルがErrorのイベントを送信するようにしてみます。
// ViewController.swift import UIKit import Sentry class ViewController: UIViewController { @IBOutlet var crashButton :UIButton! override func viewDidLoad() { super.viewDidLoad() } @IBAction func buttonTapped(_ sender : Any) { Client.shared?.send(event: Event(level: .error), completion: { (error) in if let e = error { print(e) } }) }
起動してボタンをタップします。このときにSentryへイベントが送信されているはずです。
SentryのIssuesタブを確認すると新しいIssueが作成されており、イベントが送信されていることが確認できます。
Issueは同じEventをグループ化したようなもので、同じイベントが何回飛んできたか、何人のユーザー(クライアント)から飛んできたのかがIssuesのダッシュボード上で確認できます。
イベントについて
デフォルトで取得できる情報
先ほど送信したErrorイベントで取れている情報を見てみます。一部情報に関しては伏せさせていただきます。 特にカスタマイズしない状態でもユーザー・デバイス・アプリに関する基本的な情報が一通り取れています。
送信情報のカスタマイズ
以下のようにevent
インスタンスにいくつか情報を加えて送信してみます。
// ViewController.swift import UIKit import Sentry class ViewController: UIViewController { @IBOutlet var crashButton :UIButton! override func viewDidLoad() { super.viewDidLoad() } @IBAction func buttonTapped(_ sender : Any) { let event = Event(level: .error) event.environment = "develop" event.message = "Custom event message" event.extra = ["at": "ViewController.swift:25"] Client.shared?.send(event: event, completion: { (error) in if let e = error { print(e) } }) } }
Issuesタブを確認すると新しいIssueが増えています。
Eventの詳細を確認するとenvironment
タグ、ADDITIONAL DATA
の項目がそれぞれ追加されていることが確認できます。
基本的にevent.extra
へ別途追加したい情報を入れておけば任意の情報を収集することができます。
さらにユーザーの情報も追加して、ユーザーごとのイベントトラッキングを行うこともできます。その他、詳しくはSentry Docs: Cocoa - Advanced Usage に説明がありますので、一読してみてください。
Loggerと組み合わせる
XCGLogger
すでに何らかのLoggerを使っている場合、Loggerの出力先に別途Sentryを追加することで、今までコンソールで見ていたエラーログをそのまま集約することができます。(もちろん不必要なイベントを大量に表示しても仕方ないので、取捨選択は必要だと思います。SentryでのEventのQuotaに関してはこちらに説明があります。)
今回はXCGLoggerのLogDestinationをカスタマイズして、Sentryへログを送信してみます。CarthageでXCGLoggerは導入済みとします。
初めから用意されているFileDestination.swift を参考にSentryDestination
クラスを作成します。Eventレベルはとりあえずerror一択とします。
// SentryDestination.swift import Sentry import XCGLogger class SentryDestination : BaseQueuedDestination { override func output(logDetails: LogDetails, message: String) { let event = Event(level: .error) event.message = logDetails.message event.logger = self.identifier event.extra = ["at": "\(logDetails.fileName):\(logDetails.lineNumber)", "detail": message] event.tags = logDetails.userInfo as? [String: String] ?? [:] Client.shared?.send(event: event) } }
AppDelegateも変更します。
// AppDelegate.swift import UIKit import Sentry import XCGLogger let logger: XCGLogger = { let logger = XCGLogger.default do { Client.shared = try Client(dsn: "https://xxxxxxxxxxxxxx@sentry.io/xxxxxxx") try Client.shared?.startCrashHandler() } catch let error { print("\(error)") } let sentryDestination = SentryDestination(owner: logger, identifier: UUID.init().uuidString) logger.add(destination: sentryDestination) return logger }() @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { } }
ボタンを押したときはloggerに記録するようにします。
// ViewController.swift import UIKit import Sentry class ViewController: UIViewController { @IBOutlet var crashButton :UIButton! override func viewDidLoad() { super.viewDidLoad() } @IBAction func buttonTapped(_ sender : Any) { logger.error("Something is wrong") } }
シミュレーターで実行して、同じようにボタンを押してみると.....。
SentryへEventが送信されており、ADDITIONAL DATA
にもLoggerの情報が書き込まれています。
おわりに
今回はクライアントアプリからSentryにイベントを送るところがメインになりましたが、それ以外にもダッシュボードからできることも結構多いです。うまく活用していくことでユーザーから問い合わせが来たときには既に開発サイドでエラーを把握していて、必要に応じて修正が始まっているようなスピード感での対応も可能になってくると思います。
OPTiMではサーバーサイドだけでなく、クライアントアプリが好きなメンバーも絶賛募集中です! AI/IoTと絡めた面白いアプリをご一緒に開発しましょう〜
ライセンス表記
アイキャッチ画像はSentry Logos and Branding で公式に配布されているものを使用しています。