質問

として ジェフ・アトウッドは尋ねた:「あなたのロギング哲学は何ですか?すべてのコードに散在すべきか .logthis() そして .logthat() 電話?それとも何らかの方法で事後ログを挿入しますか?」

役に立ちましたか?

解決

私のロギング哲学は、次の 4 つの部分に簡単に要約されます。

監査またはビジネス ロジックのログ記録

ログに記録する必要があるものをログに記録します。これはアプリケーションの要件から来ており、データベースに加えられたすべての変更のログ (多くの金融アプリケーションと同様) やデータへのアクセスのログ (医療業界では業界の規制を満たすために必要な場合があります) が含まれる場合があります。

これはプログラム要件の一部であるため、多くの場合、ロギングに関する一般的な説明にこれが含まれていませんが、これらの領域には重複する部分があり、一部のアプリケーションでは、すべてのロギング アクティビティをまとめて考慮することが有益です。

プログラムのロギング

開発者がアプリケーションのテストとデバッグを行うのに役立つメッセージ。また、データ フローとプログラム ロジックをより簡単に追跡して、実装、統合、その他のエラーが存在する可能性のある場所を理解するのに役立ちます。

一般に、このログはデバッグ セッションの必要に応じてオンまたはオフになります。

パフォーマンスログ

必要に応じて後でログを追加し、パフォーマンスのボトルネックや、プログラムの失敗の原因ではなく、動作の向上につながるその他のプログラムの問題を見つけて解決します。メモリ リークや重大ではないエラーが発生した場合、プログラムのログ記録と重複します。

セキュリティログ

セキュリティが懸念されるユーザーのアクションや外部システムとのやり取りをログに記録します。攻撃後に攻撃者がどのようにシステムを破壊したかを判断するのに役立ちますが、侵入検出システムと連携して新規または進行中の攻撃を検出することもできます。

他のヒント

私は安全性が重要なリアルタイム システムを使って仕事をしていますが、私の考えを理解していただければ、毎月 53 日火曜日の満月のときにのみ発生する珍しいバグを捕まえる唯一の方法はログ記録であることがよくあります。これはあなたをこのテーマに執着させるようなものなので、口から泡を出し始めたら、今すぐ謝罪します。

私はほとんどすべてをログに記録できるシステムを設計していますが、デフォルトですべてをオンにするわけではありません。デバッグ情報は、タイムスタンプを付けてリストボックスに出力する非表示のデバッグ ダイアログに送信され (削除前に約 500 行に制限されます)、ダイアログを使用してデバッグを停止したり、自動的にログ ファイルに保存したり、デバッグ ダイアログに転用したりすることができます。 DBWin32 などの付属のデバッガ。この転用により、複数のアプリケーションからのデバッグ出力をすべてきちんとシリアル化して確認できるため、場合によっては命の恩人になることがあります。ログ ファイルは N 日ごとに自動的に消去されます。私 使用済み 数値ログ レベルを使用するには (レベルを高く設定すると、より多くのデータがキャプチャされます):

  • オフ
  • エラーのみ
  • 基本的な
  • 詳しい
  • すべて

しかし、これでは柔軟性が低すぎます。バグに向かって作業するとき、大量の残骸をかき分けずに必要なものに正確にログインできるほうがずっと効率的です。それは特定の種類のトランザクションまたは操作である可能性があります。それがエラーの原因となります。そのためにすべてをオンにする必要がある場合は、自分の仕事を難しくするだけです。よりきめ細かいものが必要です。

そこで現在、フラグ システムに基づいたロギングに切り替える過程にあります。ログに記録されるものにはすべて、操作の種類を詳細に示すフラグがあり、ログに記録する内容を定義できる一連のチェックボックスがあります。通常、そのリストは次のようになります。

#define DEBUG_ERROR          1
#define DEBUG_BASIC          2
#define DEBUG_DETAIL         4
#define DEBUG_MSG_BASIC      8
#define DEBUG_MSG_POLL       16
#define DEBUG_MSG_STATUS     32
#define DEBUG_METRICS        64
#define DEBUG_EXCEPTION      128
#define DEBUG_STATE_CHANGE   256
#define DEBUG_DB_READ        512
#define DEBUG_DB_WRITE       1024
#define DEBUG_SQL_TEXT       2048
#define DEBUG_MSG_CONTENTS   4096

このログ システムはリリース ビルドに同梱されており、デフォルトでオンになっており、ファイルに保存されます。バグが平均して 6 か月に 1 回しか発生せず、それを再現する方法がない場合、バグが発生してからログを記録すべきだったと気づくのでは手遅れです。

通常、ソフトウェアは ERROR、BASIC、STATE_CHANGE、EXCEPTION がオンになった状態で出荷されますが、これはデバッグ ダイアログ (またはこれらが保存されるレジストリ/ini/cfg 設定) を介して現場で変更できます。

ああ、あと 1 つ、私のデバッグ システムは 1 日に 1 つのファイルを生成します。要件は異なる場合があります。ただし、デバッグコードが開始されることを確認してください ファイルには、日付、実行しているコードのバージョン、そして可能であれば顧客 ID、システムの場所などのマーカーが含まれています。現場からは寄せ集めのログ ファイルが届く可能性があり、何がどこから来たのか、どのバージョンのシステムが実行されていたのかという記録が実際にはデータ自体に含まれている必要があり、顧客を信頼することはできません。 /フィールド エンジニアは、自分が所有しているバージョンを教えてくれます。彼らは、自分が所有していると考えているバージョンを伝えるだけかもしれません。さらに悪いことに、ディスク上にある exe バージョンが報告される可能性がありますが、交換後に再起動するのを忘れたため、古いバージョンがまだ実行されています。コード自体がそれを教えてくれます。

それは私の脳がダンプされました...

例外が発生した場合は、メッセージとフルスタックトレースを含むログを常に追加すると思います。それ以上に、ログを頻繁に使用するかどうかはかなり主観的だと思います...

私は、ログに記録している内容がほとんどヒットしない重要な場所にのみログを追加しようとすることがよくあります。そうしないと、ログが大きくなりすぎるという彼が言及したような問題が発生します...これが、エラー ケースを常にログに記録することが理想的な理由です (そして、これらのエラー ケースが実際にいつ発生したかを確認できると、問題をさらに詳しく調査できるのは素晴らしいことです)。

他にログに記録しておくと良いことは、アサーションがあり、そのアサーションが失敗した場合にそれをログに記録することです。たとえば、このクエリの結果は 10 件未満である必要がありますが、それを超える場合は問題がある可能性があるため、ログに記録します。もちろん、ログ ステートメントがログをいっぱいにしてしまった場合、それはおそらく、それをある種の「デバッグ」レベルに置くか、ログ ステートメントを調整または削除するかのいずれかのヒントになります。ログが大きくなりすぎると、無視してしまうことがよくあります。

私は伝統的なアプローチだと思うものを採用しています。条件定義で囲まれた一部のログ。実稼働ビルドでは、定義をオフにします。

これはログ データに意味があることを意味するため、私は作業中に意図的にログを記録することにしました。

  • ログフレームワークに応じて、レベル/重大度/カテゴリ情報を追加して、ログデータをフィルタリングできるようにすることができます。
  • 多すぎず、少なすぎず、適切なレベルの情報が存在することを確認できます。
  • コードを記述するときに最も重要なものが何かがわかるため、それらが確実にログに記録されるようになります。

何らかの形式のコード インジェクション、プロファイリング、またはトレース ツールを使用してログを生成すると、冗長で有用性が低く、調査が困難なログが生成される可能性が高くなります。ただし、デバッグ補助として役立つ場合があります。

コード内で多くの条件をアサートすることから始めます (C# では、 System.Diagnostics.Assert) ですが、デバッグ中やシステムに負荷をかけているときに、デバッガーを永続的に接続せずにコード内で何が起こっているかを追跡する方法が本当に必要であると判断した場合にのみ、ログ記録を追加します。

それ以外の場合は、特別なブレークポイントとしてコードにトレースを配置する Visual Studio の機能を使用することを好みます。ブレークポイントを挿入して右クリックし、[ヒット時...] を選択して、その場合に何を表示するかを指示します)。再コンパイルする必要はなく、トレースをオンザフライで簡単に有効/無効にすることができます。

多くの人が使用するプログラムを作成している場合は、ログに記録するものとログに記録しないものを選択する何らかのメカニズムを用意することが最善です。.logthis() 関数を支持する議論の 1 つは、これらの関数が (適切に実行されれば) 場合によってはインライン コメントの優れた代替となる可能性があるということです。

さらに、エラーが発生している場所を正確に絞り込むのにも役立ちます。

すべてをログに記録し、Grep に並べ替えてもらいます。

私もアダムの意見に同意しますが、興味のあることや、実績として実証できることを、それが起こったことの一種の証拠として記録することも検討します。

さまざまなレベルを定義し、config / invocation で設定を渡します。

本当にシステムにログインする必要がある場合、テストはクソか、少なくとも不完全で、あまり徹底的ではありません。システム内のすべてのものは可能な限りブラック ボックスである必要があります。String のようなコア クラスがログを必要としないことに注目してください。主な理由は、これらのクラスが非常によくテストされ、詳細どおりに動作するためです。驚く様な事じゃない。

私は、ユーザーが指定した同じ手順を繰り返すだけでなく、単体テストで再現しない問題を絞り込む方法としてログを使用します。これらのまれな不具合は、非常に離れたハードウェアでのみ発生します (また、非常にまれではありますが、ドライバーやサードパーティのライブラリの制御外の不具合によって引き起こされる場合もあります)。

これはすべてテスト手順で把握されるべきだというコメントには同意しますが、これらの要件を満たすために非常に低レベルでパフォーマンスが重要なコードを要求する 100 万以上の LOC コードベースを見つけるのは困難です。私はミッションクリティカルなソフトウェアでは働いていませんが、グラフィックス業界で働いており、メモリ アロケーターの実装から GPU コードの利用、SIMD まで、あらゆる作業を行う必要があることがよくあります。

非常にモジュール化された、疎結合または完全に分離されたコードであっても、システムの相互作用により、プラットフォーム間で動作が異なる非常に複雑な入出力が発生する可能性があり、その場合、テストを回避する不正なエッジケースが発生することがあります。モジュール式のブラック ボックスは非常にシンプルですが、それらの間の相互作用は非常に複雑になり、場合によっては予期しないエッジ ケースが発生することがあります。

ログを記録することで問題が解決した例として、あるとき、クラッシュしていたプロトタイプの Intel マシンを使用している奇妙なユーザーがいました。SSE 4 をサポートする必要があるマシンの最小要件をリストしましたが、この特定のマシンはこれらの最小要件を満たしていましたが、16 コア マシンであるにもかかわらず、SSE 3 以降のストリーミング SIMD 拡張機能をサポートしていませんでした。SSE 4 命令が使用された行番号を正確に示す彼のログを見ることで、それをすぐに発見することができました。私たちのチームの誰も、レポートの検証に参加した他のユーザーはおろか、問題を再現できませんでした。理想的には、古い SIMD バージョン用のコードを記述するか、少なくともいくつかの分岐とチェックを行ってハードウェアが最小要件をサポートしていることを確認する必要がありましたが、簡素化と経済性のために最小ハードウェア要件を通じて伝達される確固たる仮定を作りたかったのです。おそらく、ここでは、最小システム要件に「不具合」があったのではないかと考えられます。

ここでのログの使用方法を考えると、かなり大きなログが取得される傾向があります。ただし、目標は読みやすさではありません。一般的に重要なのは、ユーザーがチームの誰も (ましてや世界の他のユーザーはほとんどいない) 何らかのクラッシュを経験したときに、レポートとともに送信されるログの最後の行です。再現できる。

それにもかかわらず、過度のログスパム送信を避けるために私が定期的に採用しているトリックの 1 つは、一度正常に実行されたコードはその後も正常に実行されると仮定するのが合理的であることが多いということです (厳密な保証ではありませんが、多くの場合は合理的な仮定です)。そこで私がよく採用するのは、 log_once 呼び出されるたびにロギングのコストを支払うオーバーヘッドを回避するための粒度関数の関数の一種。

私はログ出力をあちこちに散らばりません (時間があればそうするかもしれません)。通常、私は最も危険と思われる領域のためにそれらを最も予約します。GLSL シェーダを呼び出すコード。(ここでは、GPU ベンダーによって、機能やコードのコンパイル方法が大きく異なります)、SIMD 組み込みを使用するコード、非常に低レベルのコード、必然的に OS 固有の動作に依存する必要があるコード、OS 固有の動作に依存する必要がある低レベルのコード、 POD の表現 (例:1 バイトに 8 ビットを想定するコード) -- 同様に、多数のアサーションと健全性チェックを散りばめ、最も多くの単体テストを作成するようなケースです。通常、これで十分です。ロギングがなければ、再現不可能な問題が発生し、その問題に盲目的に突き当たり、世界で 1 人のユーザーに対する解決策の試行を何度も繰り返す必要があったであろう状況を、ロギングのおかげで何度も救われました。問題を再現できる可能性があります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top