observability について
Swift 開発者向けの可観測性スキルです。ログ、signpost、メトリクスの選択に加え、プライバシー、プロダクション計測、シンボリケーションの手順を確認できます。
主な機能
- 01用途に応じたログ・signpost・メトリクスの選択
- 02Logger のレベルとプライバシー注釈の確認
- 03デバイスログの取得とエクスポート
- 04MetricKit とクラッシュ診断の確認
- 05プロダクション計測とシンボリケーションの確認
活用シーン
- 01アプリの print ログを置き換える
- 02処理時間やユーザーフローを追跡する
- 03本番環境のクラッシュやハングを調査する
- 04メトリクスの出力と監視を設計する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ n0an/ observability-agent-skill --skill observabilityインストール後、Claude Code で/observabilityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlreferences/activity-tracing.mdanti-patterns.mdcrash-reporting.mdfundamentals.mdlog-cli-and-console.mdlogger-api.mdmetrickit.mdnetwork-telemetry.mdoslogstore.mdproduction-monitoring.mdsignposts.mdsymbolication.mdthird-party-sdks.md詳細
アプリの状態を可観測にする Swift コードを作成・レビューします。適切なシグナル(ログ、サインポスト、メトリクス、診断情報)を適切なレベルで出力し、ユーザーデータをログに含めず、実際に読み取れる形式(シンボリケーション済み、集計済み、適切なリリースに紐付け済み)で本番環境からデータを取得します。
レビューの手順:
references/fundamentals.mdを使って、シグナルモデル(3 つの柱、統合ログアーキテクチャ、レベルと永続化、開発環境と本番環境での可視性)を確立します。- 以下の判断ツリーに従って、各出力に適切なシグナルが使われていることを確認し、適切でない場合は振り分け直します(
Loggerを使うべきところでのprint、サインポストを使うべきところでのログ、MetricKit がすでに計測している箇所での独自タイマーなど)。 references/logger-api.mdを使って、Loggerの使い方(サブシステム/カテゴリ構成、レベルの選択、プライバシー注釈、補間のパフォーマンス、ラッパーの設計)を検証します。references/log-cli-and-console.mdを使って、ログ取得のワークフロー(Console.app、logCLI の述語、ログプロファイル、sysdiagnose)を検証します。references/oslogstore.mdを使って、アプリ内でのログアクセスとサポートログのエクスポートを検証します。references/signposts.mdを使って、サインポストの計測(OSSignposter、ID、インターバル、注目ポイント、Instruments との接続)を検証します。references/activity-tracing.mdを使って、フローをまたぐ相関(アクティビティ、相関 ID)を検証します。references/metrickit.mdを使って、MetricKit の導入(サブスクリプションのタイミング、ペイロードの処理、ヒストグラム、診断情報、iOS 27 のMetricManagerAPI)を検証します。references/crash-reporting.mdを使って、クラッシュ/ハング/OOM の可観測性(レポートに含まれる情報と含まれない情報、終了理由、Organizer)を検証します。references/symbolication.mdを使って、シンボリケーション(dSYM の取り扱い、UUID の照合、atos、MetricKit のコールスタック)を検証します。references/production-monitoring.mdを使って、本番環境のパイプライン(エクスポート形式、単位、集計戦略、起動メトリクスの偏り、アラート)を検証します。references/network-telemetry.mdを使って、ネットワークリクエストの可観測性(エラー分類とノイズのフィルタリング、非致命的レポートの設計、URLSessionTaskMetrics、集計モニタリング)を検証します。references/third-party-sdks.mdを使って、サードパーティ SDK の統合(クラッシュハンドラーの排他性、dSYM のアップロード、ブリッジング、プライバシー)を検証します。references/anti-patterns.mdを使って、よくある間違いを見つけます。
作業の一部だけを行う場合は、関連する参照ファイルのみを読み込んでください。
タスク別の参照先
ユーザーの目的に合わせて、読み込む順序を決めてください。必要なものだけを読み込みます。
「アプリに適切なログを追加する/print 文を置き換える」
references/logger-api.md- Logger の設定、カテゴリ、レベル、プライバシーreferences/fundamentals.md- どのレベルが永続化され、誰が何を見られるか
「補間した値が と表示される/ログ内のユーザーデータを隠したい」
references/logger-api.md- プライバシー注釈、.private(mask: .hash)、等価性の追跡
「デバイスからログを読み取る/Console のノイズをフィルタリングする/デバッグ中にログを取得する」
references/log-cli-and-console.md- Console.app のワークフロー、log stream/show/collect、述語、シミュレーター/デバイスでのスクリプトによる取得の段階
「ユーザーにログを送ってもらう/アプリ内にログを表示する」
references/oslogstore.md- OSLogStore、対象範囲の制限、エクスポートの設計references/logger-api.md- 永続化ルールによって、取得可能なログが決まります
「操作にかかる時間を測定する/遅い箇所を見つける」
references/signposts.md- OSSignposter のインターバル、Instruments の os_signpostreferences/production-monitoring.md- 計測データを実運用環境から取得する必要がある場合
「サブシステムをまたぐユーザーフローを追跡する/関連するログを相関付ける」
references/activity-tracing.md- アクティビティ、相関 ID、サインポストのほうが適している場合
「本番環境からパフォーマンス/バッテリー/起動メトリクスを収集する」
references/metrickit.md- MXMetricManager、ペイロード、ヒストグラム、カスタム mxSignpost メトリクスreferences/production-monitoring.md- エクスポート、集計、アラート
「本番環境でアプリがクラッシュする/応答しなくなる/強制終了されるのはなぜですか?」
references/crash-reporting.md- クラッシュのパイプライン、終了理由、OOM、ウォッチドッグ、応答停止references/metrickit.md- 診断ペイロードにスタックが含まれますreferences/symbolication.md- スタックを読みやすくします
「このクラッシュログ/MetricKit スタックをシンボリケートしたい」
references/symbolication.md- dSYM、UUID の照合、atos のレシピ
「Sentry/Crashlytics/Datadog をセットアップしたい、または必要か判断したい」
references/third-party-sdks.md- MetricKit に追加される機能、統合時のルールreferences/crash-reporting.md- これらが競合するネイティブの基本機能
「メトリクスダッシュボードを構築したい/テレメトリーをバックエンドに送信したい」
references/production-monitoring.md- ASC Power and Performance API、パイプラインの設計、単位、バケット、インデックスメトリクスreferences/metrickit.md- エクスポートするペイロードの形式
「ネットワークエラーを監視したい/本番環境で遅いリクエストを診断したい」
references/network-telemetry.md- エラーの分類、致命的でないレポートの設計、URLSessionTaskMetrics、集計監視references/third-party-sdks.md- 既製の RUM/パフォーマンス SDK を使用している場合
「バグが発生する/ログがない/ペイロードが届かない」
references/anti-patterns.md- よくある原因references/fundamentals.md- 永続化と配信のルール
判断ツリー
どのシグナルを出力すべきですか?
Recording a discrete event to reconstruct behavior later?
-> Logger (unified logging), with the level matching persistence needs
Measuring the duration of an operation for local profiling?
-> OSSignposter interval + Instruments os_signpost / Points of Interest
Measuring durations or counts across the production fleet?
-> mxSignpost (MetricKit histograms) or your own telemetry event
Daily launch / hang / battery / disk / memory numbers from production?
-> MetricKit metric payloads (MXMetricPayload)
Crash, hang, CPU-spin, disk-write, or OOM call stacks from production?
-> MetricKit diagnostic payloads + Xcode Organizer (+ third-party if you
need real-time alerting and grouping)
Correlating logs that belong to one user flow across subsystems?
-> os_activity scopes or an explicit correlation ID in the message
Users need to send you what happened on their device?
-> OSLogStore export in-app, or a sysdiagnose for deep system issues
Real-time dashboards, alerting, release health, session context?
-> third-party SDK (Sentry/Crashlytics/Datadog) or your own pipeline
(OpenTelemetry + OTLP backend is the vendor-neutral build-it route)
どのログレベルを選ぶべきですか?
Only useful while actively developing, high volume? -> debug / trace
Helpful context, fine if it evaporates after the fact? -> info
Essential for troubleshooting, must survive on device? -> notice (default)
Something failed but the app copes? -> error
A bug: broken invariant, data loss risk, must stand out? -> fault / critical
永続化の動作はレベルによって異なります。debug はディスクに書き込まれず、info は
log collect の実行中にのみ書き込まれ、notice/error/fault はストアに
保持されます(ローテーションまで)。サポート担当者がユーザーのデバイス上でログを確認する必要がある場合は、
notice 以上にする必要があります。
コアの指示
- アプリのロギング手段として
printやNSLogを絶対に使用しないでください。printはデバッガーの外では見えず、永続化もされません。NSLogは同期処理で遅いためです。Logger(iOS 14+)またはos_log(iOS 10+)を使用してください。 - サブシステムとカテゴリの組み合わせごとに
Loggerを1つ作成し、static letとして宣言してください。サブシステムにはバンドル識別子を、カテゴリには機能領域(networking、persistence、ui)を使用してください。アプリ全体のログを1つのカテゴリに集約しないでください。 Loggerを、通常のStringを受け取る関数で絶対にラップしないでください。OSLogMessageの補間は、メッセージが実際に出力される場合にのみ遅延評価されますが、String引数では呼び出しのたびに評価されます。抽象化が必要な場合は、@autoclosure () -> Stringを受け取るか、カテゴリごとのLoggerを直接公開してください。- 補間された文字列やオブジェクトは、デフォルトで非公開として扱ってください(デバッガーを接続していないデバイスでは
<private>としてログに記録されます)。ユーザーデータが含まれていないことを確認してから、値にprivacy: .publicを指定してください。値を明かさずに同一性を照合する必要がある場合は、privacy: .private(mask: .hash)を使用してください。 - 個人識別情報(氏名、メールアドレス、トークン、認証情報を含む URL、口座番号)を
.publicとして絶対に記録しないでください。また、OS の秘匿化機能で保護できないファイルやサードパーティ SDK にも書き込まないでください。デバイスを持っている人なら誰でも、Console でアプリの永続化されたログメッセージを読めます。 - レベルは雰囲気ではなく、永続化の有無とコストに基づいて選んでください。
debug(メモリのみ。キャプチャされない場合のコストはほぼゼロ)、info(メモリ上に保持され、log collectの実行中にのみ永続化)、notice(デフォルトで永続化)、error(永続化。アプリが処理する失敗向け)、fault/critical(永続化され、追加の状態をキャプチャ。実際のバグ向け)です。faultは予期される失敗ではなく、プログラミング上のエラーに限定してください。 - 静的な書式文字列だけを使用してください。値は補間し、最初に文字列を連結してメッセージを作らないでください。文字列連結はプライバシー注釈と遅延評価の両方を損ないます。
- 所要時間の計測には、タイムスタンプ付きのログ行ではなく
OSSignposterのインターバルを使用してください。操作のインスタンスごとにOSSignpostID(makeSignpostID())を生成し、返されたOSSignpostIntervalStateを保存して、同じ名前で終了してください。専用の Instruments トラックに表示したいイベントには.pointsOfInterestカテゴリを使用してください。 - リリースビルドでもサインポストを有効にしてください。サインポストは低コストになるよう設計されており、リリースビルドの Instruments プロファイリングにはサインポストが必要です。必要に応じて、高頻度のものは
OSSignposter.disabledまたは環境チェックで制御してください。 - MetricKit(
MXMetricManager.shared.add(_:))には、起動経路のうち必ず実行される場所(アプリの初期化処理/didFinishLaunching)で、早い段階に一度だけ登録してください。pastPayloadsがデータを返すのは、アプリが登録済みの場合のみです。登録が遅れたり、条件付きだったりすると、履歴が通知なしに失われます。 - MetricKit は実機でのみデータを配信します。シミュレーターでのデバッグは絶対に行わないでください。デバイスを接続した状態で、Xcode の「Debug」>「Simulate MetricKit Payloads」を使用してください。メトリクスのペイロードは24時間に最大1回(iOS 15以降では診断情報は即時、クラッシュ診断は次回起動時)届くことを想定してください。
- MetricKit のサブスクライバーコールバックはバックグラウンドキューで呼び出されます。UI に触れず、ペイロードを速やかに永続化または転送してください。ペイロードオブジェクトには、エクスポート用の
dictionaryRepresentation()/jsonRepresentation()が用意されています。 - MetricKit のヒストグラムをエクスポートする際は、Apple の単位(
Measurementの値、ペイロード固有の単位によるバケット)を維持し、変換は境界で一度だけ行ってください。変換処理を書く前に、すべてのメトリクスの単位を確認してください。秒とミリ秒の不一致を見逃すと、誰も気付かないまま1週間分のダッシュボードが壊れます。 - コールド起動を、プリウォーミングを考慮せずに「プロセス開始から didFinishLaunching まで」として絶対に計測しないでください(環境内の
ActivePrewarmを確認してください)。iOS 15以降では、ユーザーがタップするずっと前にシステムがプロセスを開始することがあり、実際には存在しない数分間の起動時間が生じます。histogrammedTimeToFirstDrawを MetricKit から取得する方法を優先してください。 - p95/p99 だけでなく、平均値と低パーセンタイルも確認してください。プリウォーミングによる外れ値やクランプ処理は分布の中間部分の回帰を隠します。全体像を把握するには、平均化する前にメトリクスを0~100の範囲に収まるスコアへ変換してください。
- OOM による終了では、クラッシュレポートは生成されません。
MXAppExitMetricのメモリ関連の終了理由(および新しい API のメモリ例外診断情報)で検出し、バックグラウンド時のメモリ使用量を削減してください。クラッシュレポーターに表示されるのを待たないでください。 - メモリ警告を受けたら、圧縮されたコレクションを急いで走査して解放するのではなく、キャッシュポリシーを変更してください(キャッシュを停止し、サイズに上限を設け、自動的に削除する
NSCacheを優先します)。圧縮されたページに触れると展開が発生し、最も避けたいタイミングでメモリ使用量が急増します。 - 1つのアプリに、クラッシュレポート SDK を2つ、またはクラッシュ SDK と独自の Mach/シグナルハンドラーを併用して絶対に導入しないでください。ハンドラーの連鎖は不安定で、互いのレポートを破損させます。どれか1つを選んでください。MetricKit はプロセス外で収集するため、競合しません。
- 出荷するすべてのビルドの dSYM をアーカイブし、シンボリケーションの前に UUID が一致すること(
dwarfdump --uuid)を確認してください。MetricKit のコールスタックは、binaryUUIDとオフセットのみの未シンボリケーション状態で届きます。デバイス外でatos -iを使ってシンボリケーションしてください(-iはインライン化されたフレームを復元します。ロードアドレスはフレームアドレスからoffsetIntoBinaryTextSegmentを引いた値です)。シンボル付きでビルドを App Store に提出すると、Organizer のレポートにシンボル名とソースへのジャンプが表示されます。 - ハングはメインスレッドの CPU 使用率で切り分けてください。ほぼ100%なら処理自体が遅すぎます(Time Profiler の対象)。ほぼアイドル状態なら、I/O、ロック、IPC、優先度逆転によるブロックです(System Trace の対象)。CPU をさらに詳細にプロファイリングしても、ブロックされたスレッドは見つかりません。ハングは250 msから報告されるため、500 ms以上は深刻な問題として扱ってください。
- シミュレーターまたはデバッガー接続中に、ウォッチドッグの動作を絶対に検証しないでください。どちらでも起動ウォッチドッグは無効です。サポート対象のうち最も古いデバイスで単体起動テストを行い、Organizer の回帰通知を有効にしてください。ハングや終了の割合が増加した場合、ユーザーではなくあなたに通知されます。
OSLogStoreはメインスレッド以外で読み取ってください。iOS では自プロセス(.currentProcessIdentifier)に限定され、永続化ルールによって保持された内容のみが返されます。本番環境ではdebug/infoメッセージは取得できません。- フローをまたいでログを関連付けるにはアクティビティ(
os_activity)または明示的な相関 ID を使用し、所要時間の計測にはサインポストを使用してください。アクティビティは関連付け、サインポストは時間計測に使います。一方をもう一方の用途に無理に使わないでください。 - ネットワークエラーは選別して報告してください。デコードおよびデコード後の検証失敗は契約違反なので、すべてのユーザーから非致命的エラーとして報告する価値があります(読みやすい説明を持つカスタムエラーでラップしてください。共有ドメインによって異なる問題が1つのダッシュボード行にまとめられる生の
URLErrorは絶対に使わないでください)。オフライン、接続切断、タイムアウトによるノイズは集計し、こちらが発行したキャンセルは除外してください。遅いリクエストはURLSessionTaskMetricsの各フェーズ(DNS、接続、TLS、最初のバイトまでの時間、isReusedConnection)で診断してください。references/network-telemetry.mdを参照してください。 - 最適化の前に計測してください。本番環境のみで発生する問題を調査する場合は、まず仮説を裏付けるメトリクス/診断情報を追加し、リリースしてデータを確認してください。再現しないローカル環境での再現から推測しないでください。
- ノイズではなく、判断ポイントを記録してください。状態遷移、分岐結果、境界での入力、すべてのエラー経路を記録します。毎フレームやスクロールのたびに出力されるログはサインポストの内側に置くか、削除してください。
出力形式
ユーザーがレビューを依頼した場合は、ファイルごとに指摘事項を整理してください。各指摘事項について:
- ファイル名と該当する行を示します。
- 置き換えるべきアンチパターンを挙げます。
- 簡潔な修正前/修正後のコードを示します。
問題のないファイルは省略してください。最後に、優先度の高い変更から順にまとめを記載します。
ユーザーが可観測性コードの作成または修正を依頼した場合は、指摘事項のレポートを返すのではなく、直接変更してください。
出力例:
CheckoutService.swift
31 行目:本番環境の失敗時に print を使用しています。出力が見えず、永続化もされません。
// Before
print("Payment failed for \(user.email): \(error)")
// After - persisted, categorized, PII kept private
private static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!, category: "checkout")
Self.logger.error("Payment failed for \(user.email, privacy: .private(mask: .hash)): \(error.localizedDescription, privacy: .public)")
58 行目:独自のタイミングログを使用しています。代わりにサインポストの区間を使用してください。
// Before
let start = Date()
let receipt = try await submit(order)
logger.info("submit took \(Date().timeIntervalSince(start))s")
// After
private static let signposter = OSSignposter(
subsystem: Bundle.main.bundleIdentifier!, category: "checkout")
let id = Self.signposter.makeSignpostID()
let state = Self.signposter.beginInterval("SubmitOrder", id: id)
let receipt = try await submit(order)
Self.signposter.endInterval("SubmitOrder", state)
まとめ
- プライバシー(高): メールアドレスをハッシュ化してマスキングしてください。現在、生の値が永続化されるエラーメッセージに公開されています。
- シグナルの選択(中): すべての所要時間のログ記録をサインポストに移行し、Instruments と MetricKit で集計できるようにしてください。
出力例終わり。
リファレンス
references/fundamentals.md- 可観測性モデル:Apple プラットフォームにおける3つの柱、統合ログアーキテクチャ(バイナリストア、メモリとディスク)、レベルと永続性、開発時と本番環境で確認できる情報、シグナルマップ。references/logger-api.md-Loggerと従来のos_log:セットアップパターン、レベル、プライバシー注釈とマスキング、フォーマットと配置、遅延補間、ラッパーと複数の出力先を持つファサードの設計。references/log-cli-and-console.md- Console.app のワークフロー、log stream/show/collect/config、述語構文、ログ設定プロファイル、sysdiagnose、エージェント支援デバッグのスクリプト化されたキャプチャワークフロー(シミュレーターのストリームをファイルに出力、環境変数で制御する print ミラーを使ったdevicectlコンソール起動、タイムスタンプ付きのsudo log collectによるデバイスアーカイブ)。references/oslogstore.md- 実行時に独自のログを読み取る方法、スコープと永続性の制限、サポートログのエクスポートの作成。references/signposts.md-OSSignposterのインターバル/イベント/アニメーションインターバル、サインポスト ID、従来のos_signpost、Instruments の os_signpost と Points of Interest、xctrace、XCTest メトリクス、計測できないプロセス向けの DTrace プローブ。references/activity-tracing.md- Swift でのos_activity、スコープ、labelUserAction、相関 ID、アクティビティとサインポストの違い。references/metrickit.md-MXMetricManager/MXMetricPayload/MXDiagnosticPayload(iOS 13-25)、iOS 27 のMetricManager非同期 API、ペイロードの配信間隔、ヒストグラム、カスタムmxSignpostメトリクス、拡張起動トラッキング、テスト。references/crash-reporting.md- レポートに含まれるものと含まれないもの:クラッシュパイプライン、例外の種類、ウォッチドッグコード、終了理由、OOM、ハング対応ツールの一覧(Thread Performance Checker、デバイス上の検出、Organizer のハングレポート)、Xcode Organizer のペインとリグレッション。references/symbolication.md- dSYM、UUID の照合、atos、デバッグ情報の階層とシンボルがストリップされた場合の動作、Swift のデマングリング、dyld 共有キャッシュ、.ips ファイルと MetricKit のコールスタックツリーのシンボリケーション、SDK シンボルのアップロード。references/production-monitoring.md- 出荷後のテレメトリ:App Store Connect Power and Performance API(メトリクス、スマートインサイト、診断シグネチャ/ログ)、iOS での OpenTelemetry(opentelemetry-swift、OTLP エクスポーター、ゲージ/カウンター/ヒストグラムの意味、TaskLocal によるスパンの伝播、withSpan ファサードの設計、詳細度の制御)、エクスポートパイプラインの設計、単位とバケット、ラボと実環境、起動メトリクスの偏り(プリウォーミング)、スコアインデックス、パーセンタイルと平均値、アラート。references/network-telemetry.md- リクエスト単位のネットワーク可観測性:ラボ環境では実環境の状況を過小評価してしまう理由、レポート/カウント/ドロップのエラー分類、非致命的エラーの設計(読みやすいカスタムエラー、userInfo のコンテキスト、埋め込みブレッドクラム)、URLSessionTaskMetricsの各フェーズと診断(キープアライブ、サーバー処理時間、レイテンシの物理的要因)、集約モニタリング(Firebase Performance/OTel)、対象を絞った完全ダンプの収集。references/third-party-sdks.md- Sentry/Crashlytics/Datadog/Firebase とネイティブスタックの比較、クラッシュハンドラーの排他性、統合ログのブリッジ、dSYM アップロードの自動化、プライバシーマニフェストとコスト。references/anti-patterns.md- ログとテレメトリのコードを生成する際に LLM がよく犯す間違い。