ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
構造化ロギングによるデバッグ
Xcode 15のデバッグココンソールを使い、どのようにロギングを通して、アプリの調査経験を向上させることができるかを学びます。高度なフィルタリングと改善された視覚化を使用して、簡単かつ効率的にログを辿る方法を探ります。また、デバッグ中にコード内の式を評価するためにdwim-print コマンドを使用する方法を紹介します。
関連する章
- 0:44 - Tour of the Debug Console
- 3:28 - Live debugging
- 7:25 - LLDB improvements
- 9:04 - Tips for logging
- 12:04 - Get the most out of your logging
リソース
関連ビデオ
WWDC23
WWDC20
WWDC18
-
ダウンロード
♪ ♪
皆さん こんにちは 私はXCodeデバッガUIチーム エンジニアのNathanです 本日はXcode 15に搭載される 新しいデバッグコンソールを 紹介します
このセッションでは まずデバッグコンソールについて 簡単に説明していきます 次にデバッグコンソールの利点を 紹介していきましょう 私自身のアプリのバグを 診断してみます それからLLDBに導入される 改善点を紹介します そして最後にAppleの統一ログの APIを活用するための ヒントを紹介します
ではまず デバッグコンソールの 新機能について説明します 私のデバイスでアプリ「Backyard Birds」を 起動しています これは ユーザーがバックヤードを管理し バーチャルな鳥の世話をするアプリです
アプリケーション起動後 デバッグコンソールに 多くのログが入力されています すぐにコンソールに これまで使っていたメタデータが ついていないことに 気がつきました 代わりに デベロッパが見てもらいたい メッセージに注目し まだ表示を見たい場合には デバッグコンソールの左下にある メタデータのボタンを選択します ニーズに最も適したタイプを 選択します この場合 タイプ ライブラリー サブシステム カテゴリーを 選択します
これで メタデータの設定が 有効になります コンソールの各ログの下に表示され より小さくより目立たないようになっていて 意図された出力を 妨げないようにするため 黄色や赤の背景のログに 接することもありますが これらはより重要度が高く エラーや不具合があることを 示すものです すべてのログのメタデータを 同時に見たくない場合 コンソールを使えば 個々のログのメタデータを 見ることができる コンソールで 該当するログを 選択することで そのログのメタデータを調べることが 可能です これでポップアップ・ウィンドウが表示され 利用可能な全メタデータが表示される これにはコールサイトのような情報も 含まれています ログを出した元の機能の名前が 表示されます この追加メタデータを確認するのは重要でも 新しいデバッグ・コンソールの 最大の利点は フィルタリング機能なのです コンソールが重要でないログで いっぱいになりがちだけれど Xcode 15ではフィルタリングが これまで以上に簡単になったのです これでコンソールは複雑な条件付き フィルタリングを実行できるようになり 簡単に重要なログを見つけることが できるようになりました コンソールはまたこれらのフィルタを 様々な作成方法があり もちろん入力可能です
フィルターは フィルターバーに 直接入力もできます
その際自動補完ポップオーバーが 表示されて 入力しようとしている フィルタを 作成する手助けをしてくれます
さらに フィルター・メニューは特定の 種類のログに素早くアクセスできるので 見たい種類のログを選ぶことができます
最後に ある程度興味を持ったログを 逆クリックすると コンソールには似たようなログを隠したり 表示するオプションがあります このように見たいログに 焦点を当てたり あるいは除外もできます
これらのフィルタリング方法によって すべての出力データを迅速かつ 効率的に切り分け 最も関連性の高い ログを探し出すことができます では 新しいデバッグ・コンソールを使って アプリケーションの実際の 問題を見つけ修正してみましょう 一部のユーザーがプロフィールを 更新した後 内容が保存されていないことに 気づいたという報告を受けました 最新のデバッグコンソールを活用して 上手なログインの仕方を 実践してみましょう 私はこのバグの原因を素早く 簡単に特定できます
まず タブバーで「アカウント」を 選択して この問題を再現してみましょう 鉛筆を選択してアカウントを 編集するを選択します
最後に表示名をかえてみましょう
これでうまくいったように見えましたが ページを閉じて自分のアカウントを確認すると 変更は消えているようです さて いくつか思い当たることがありますが 新しいデバッグコンソールがこの問題を解決し 問題の根源を突き止める 手助けをしてくれます これらの手順を行うと デバッグコンソールに 多くの出力が表示されます 新しいコンソールで多くのログの中から 自分の興味を持ったものを 見つけることができます この場合 アカウント管理だけに特化した カテゴリーです これらに焦点を絞るために、 プロジェクトのすべてのカテゴリーに を含む すべてをフィルターに入力し ポップアップから カテゴリーフィルターを選択します
これで 私のコードのアカウント関連から 全てのログインが表示されます このフィルターをセットすれば 出力はずっとあつかいやすくなります これらのログのいくつかは 私が要求した 「プロパティを設定する」表示名です なぜアプリは期待通りに 動かなかったのかを検証します このコードがある場所を ログにカーソルを合わせて 右下にあるソースの場所を 選択してみます
Xcodeはソースのログに移動するので この表示名を設定します ソースコードを確認してみると 現在のアカウントでsetDisplayNameを 呼び出しているようです この問題をより詳しく検証するために アカウント情報の更新を担う関数に 移動してみましょう このコードをさらに見直したら セントラル・アカウント・データベースに 変更を送信している間に ローカルアカウントのキャッシュを 更新するのを忘れていたようです データベースを更新した後 ローカルの表示名を 新しいものに設定する 必要があります。
ついでにメールアドレスの同じバグにも 気づきました この問題は同じ方法で修正できます
次の行にブレークポイントを使って 疑惑を検証し これで問題が解決したのかを 確認しましょう
アプリケーションを作り直し ここで一時停止するために 前のステップを再作成します この位置まできたら 私の疑念が 正しいかどうか確認してみたい そのために アカウントの現在の状態に "po"と入力してみます 想定している古いデータをチェックします
困ったことに この対象のアドレスだけを 受け取ったようです どういうわけでしょうか 「po」 はごく一般的な表現ではあるが 私が実行したい表現ではないことが わかりました 私はこのクラスに対して独自の デバッグdescriptionを定義していないのです 実際にこのケースでは、"p"を実行したいのです ではやってみましょう
まさにこれは 私が求めていたものであり 表示名はデータベースの更新によって 単独で設定された訳ではないという 私の疑念を裏付けたものでした 追加した行を進み 表示名が更新されたことを 確認しましょう
バッチリです この問題は解決したようです これで鳥に餌をやる 時間に戻れます Xcode 15でかなり単純だったものから 進歩したLLDBにログインします あのバグを解決しようと していたことを思い返すと 私は 「po」を間違えた場所に 入力したことに気が付きました 式の実行時間を遅らせてしまうか 最悪の場合は CustomStringConvertibleを 実行していないと単純に プロパティのアドレスが 戻ってきてしまうことです これにはイライラさせられてしまい もっといい方法があればと思うのです そこで"p"を実行したところ 適切な結果が得られました しかし「expression」のように 様々なコマンドがあります 「v」「vo」「フレーム変数」 その他覚えるべきことが たくさんあります これは実行するのが 難しそうなので デベロッパを支援するために Do What I Mean Printを 紹介します Do What I Mean Printは ひとつのコマンドでコード内の さまざまな式使用して 1つのコマンドで評価することで 時間を節約することが 可能になります
変数をチェックするたびにこのような長い コマンドを入力するのは 面倒でしょうから 以前の"p"を 「Do What I Mean Print」の 略名で実行してみます これにより 大半の使用例で "p"を実行するだけで済みます 実際にカスタムオブジェクトの 説明を印刷したい場合のために Do What I Mean Print の オブジェクトの説明フラグを付けて 実行することができます 以前の略名 「po」 を置き換えて 「何をプリントするのか」を カスタムオブジェクトの説明欄で 実行するようにしました 従来であれば意図した出力を得るために 複数の異なるコマンドを 実行する必要があった 多くの異なる表現に対して 2つのコマンドの片方を 実行できるようになります 最後に誰もがロギングを最大限に活用し デバッグの経験を向上させより説明します 再現が困難な問題や ユーザーからの報告に依存する問題を 効果的に特定して 解決できるようにする方法について 説明していきます 標準I/OはコマンドラインUIのための もので OSLogはデバッグ用だと まずこう言っておきます したがってプログラム実行中のログを 記録するために使うべきではありません エンドユーザーからの構造化ログ取得は OSLogを使用するほうががはるかに効果的です デバッグの構造体の保持だけでは ありません それでは 標準I/Oから OSLogへの変換がいかに簡単か いくつかの例を挙げてみましょう これは単純な関数です。 追加ログインをしてみたいと思います タスクをログに実行したり それを記録するのも よい習慣になります 少しだけ私が知っている 最善の方法で補足させてください これでこのコードに従うための いくつかの簡単な 「print」文の追加が完了しました タスクが完了したら その結果を出力するのと同様に この関数において今行なっているアクションを 表示します。 プロジェクトの中で これを続けていると このアウトプットすべてが どこからかなのか見つけるのが難しくなるため 多くの人と同じように 私はマーカーを付け始めました これは収拾がつかなくなって きているような気がします この追加出力のおかげでコンソールが ごちゃごちゃになってしまいました この全てのメタデータを 手間をかけずに活用できる方法が あればいいのですが 結論から言うとOSLogは 私が望んでいることをやってくれます ユニファイドロギングを活用して この機能のアップデートをしてみます まずはOSLogをプロジェクトにインポートし ログハンドルを作成します こうしてログハンドルを作成します ここでサブシステムとカテゴリを 指定します ログが表示されることを 望んでいる これらの文字列のうちで デバッグのフィルタリングを容易にするのが サブシステムのバンドルidentifierを使う方法で クラス名またはコンポーネント名を カテゴリに指定する ロガーを作成したら提供された機能を 呼び出す必要があります ログレベルを指定し 表示したいメッセージを提供します この方がはるかに読みやすく 結果的に大幅にコードが削減されます コンソールに実行するとどのように 表示されるかを見てみましょう
以下の2つのログを分けて このメタデータが どこから来ているのかを 検証してみましょう 指定したログアウトプットには 出力しようとしたメッセージと 指定した追加メタデータが 有効になっていれば その下に表示されます このメタデータの一部は メッセージやレベルなどの最初に the initial log 作成した所から 収集されたものです その他のものはサブシステムや カテゴリーなどの繰り返しを 省くために ログハンドルを 作成したとき収集しました いくつかは バックグラウンドで 処理される これらにはタイムスタンプ ライブラリ名 プロセスID スレッドID などが含まれます これらの情報は 必要なときに大いに役立ちますが 必要性に関係なくすべてのログに出力すると 膨大なスペースを取ってしまいます 幸いに新しいデバッグコンソールを カスタマイズできるので 思う存分使うことができます 最後に ログインを最大限に活用するために アプリケーションを構築する際に この事をよく考えておいてください 様々なコンポーネントに それぞれのログハンドルを 作成する必要があります 根本的なメタデータに アプリケーションのセクションに 適切な検索条件を設定できます 最も適切なログを より迅速に見つけることができます さらにOSLogStoreを活用すれば 現場でアプリケーションに 問題が発生した場合 貴重な診断データを収集します 最後に OSLogは トレース機能です つまりInstrumentsなどのツールを使って アプリケーションの複雑な パフォーマンス分析が可能です この例では ロギング・プロファイリング・ テンプレートを使って アプリケーションのパフォーマンスを 分析しています さて先ほどの内容を振り返ってみて 自分のプログラミング経験を どのように向上させていくのかを 考えていきましょう ログインに関するあらゆるニーズに 対応するため 多くの改良を重ねたXcode 15の 新しいデバッグコンソールを 活用する必要があると思います 次にコードを標準I/OからOSLogに移行する 新しいデバッグ・コンソールが提供する すべての新機能を利用できるように する必要があります ぜひLLDBの新しい 何をプリントするのかまたは "p"のコマンドを試してみてください 様々な点検をする場合は 必ずこれを使用するようにしてください 最後にAppleの統合ログAPIについて 過去のセッションをご参照ください 「Measuring Performance Using Logging」 そして「Explore logging in Swift」 ログ付けを楽しんで! ご覧いただきありがとうございました
-
-
5:17 - Calling setDisplayName from Edit Account page
.onSubmit { logger.info("Requesting to change displayName to \(displayName)") accountViewModel.setDisplayName(displayName) }
-
5:34 - Account Data Setters (Before Fix)
public func setDisplayName(_ newDisplayName: String) { logger.info("Sending Request to update DisplayName") Database.setValueForKey(Database.Key.displayName, value: newDisplayName, forAccount: account.id) logger.info("Updated DisplayName to '\(newDisplayName)'") } public func setEmailAddressName(_ newEmailAddress: String) { logger.info("Sending Request to update EmailAddress") Database.setValueForKey(Database.Key.emailAddress, value: newEmailAddress, forAccount: account.id) logger.info("Updated EmailAddress to '\(newEmailAddress)'") }
-
6:04 - Account Data Setters (After Fix)
public func setDisplayName(_ newDisplayName: String) { logger.info("Sending Request to update DisplayName") Database.setValueForKey(Database.Key.displayName, value: newDisplayName, forAccount: account.id) account.displayName = newDisplayName logger.info("Updated DisplayName to '\(newDisplayName)'") } public func setEmailAddressName(_ newEmailAddress: String) { logger.info("Sending Request to update EmailAddress") Database.setValueForKey(Database.Key.emailAddress, value: newEmailAddress, forAccount: account.id) account.emailAddress = newEmailAddress logger.info("Updated EmailAddress to '\(newEmailAddress)'") }
-
6:35 - po account
(lldb) po account
-
6:39 - po account (with result)
(lldb) po account <Account: 0x60000223b2a0>
-
7:00 - p account
(lldb) p account
-
7:04 - po account (with result)
(lldb) p account (BackyardBirdsData.Account) =0x000060000223b2a0 { id = 3A9FC684-8DFC-4D7D-B645-E393AEBA14EE joinDate = 2023-06-05 16:41:00 UTC displayName = "Sample Account" emailAddress = "sample_account@icloud.com" isPremiumMember = true }
-
7:18 - p account (after fix)
(lldb) p account (BackyardBirdsData.Account) =0x000060000223b2a0 { id = 3A9FC684-8DFC-4D7D-B645-E393AEBA14EE joinDate = 2023-06-05 16:41:00 UTC displayName = "Johnny Appleseed" emailAddress = "johnny_appleseed@icloud.com" isPremiumMember = true }
-
9:43 - Login Method Skeleton
func login(password: String) -> Error? { var error: Error? = nil //... loggedIn = true return error }
-
9:56 - Login Method with Print Statements
func login(password: String) -> Error? { var error: Error? = nil print("Logging in user '\(username)'...") … if let error { print("User '\(username)' failed to log in. Error: \(error)") } else { loggedIn = true print("User '\(username)' logged in successfully.") } return error }
-
10:18 - Login Method with Extended Print Statements
func login(password: String) -> Error? { var error: Error? = nil print("🤖 Logging in user '\(username)'... (\(#file):\(#line))") //... if let error { print("🤖 User '\(username)' failed to log in. Error: \(error) (\(#file):\(#line))") } else { loggedIn = true print("🤖 User '\(username)' logged in successfully. (\(#file):\(#line))") } return error }
-
10:40 - Login Method with Partial OSLog Transition
import OSLog let logger = Logger(subsystem: "BackyardBirdsData", category: "Account") func login(password: String) -> Error? { var error: Error? = nil print("🤖 Logging in user '\(username)'... (\(#file):\(#line))") //... if let error { print("🤖 User '\(username)' failed to log in. Error: \(error) (\(#file):\(#line))") } else { loggedIn = true print("🤖 User '\(username)' logged in successfully. (\(#file):\(#line))") } return error }
-
11:00 - Login Method with OSLog Statements
import OSLog let logger = Logger(subsystem: "BackyardBirdsData", category: "Account") func login(password: String) -> Error? { var error: Error? = nil logger.info("Logging in user '\(username)'...") //... if let error { logger.error("User '\(username)' failed to log in. Error: \(error)") } else { loggedIn = true logger.notice("User '\(username)' logged in successfully.") } return error }
-
11:16 - Example Logging Statements
let logger = Logger(subsystem: "BackyardBirdsData", category: "Account") logger.error("User '\(username)' failed to log in. Error: \(error)") logger.notice("User '\(username)' logged in successfully.")
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。