8

C# アプリケーションにロギングまたはトレースを追加したいのですが、ログの詳細レベルが低く設定されているためにメッセージがログに記録されない場合に、文字列のフォーマットや値の計算のオーバーヘッドをログに記録したくありません。

C++ では、プリプロセッサを使用して、次のようにコードがまったく実行されないようにするマクロを定義できます。

#define VLOG(level,expr) if (level >= g_log.verbosity) { g_log.output << expr; }

次のように使用します。

VLOG(5,"Expensive function call returns " << ExpensiveFunctionCall());

C# でどのように行うのですか?

ここでトレースとデバッグ機能を説明している Microsoft ドキュメントを読みました。#undef DEBUG と #undef TRACE を使用すると、生成された実行可能ファイルからすべてのトレースとデバッグ コードが削除されると主張していますが、実際には呼び出し全体が削除されますか? つまり、私が書くなら

System.Diagnostics.Trace.WriteLineIf(g_log.verbosity>=5,ExpensiveFunctionCall());

TRACE の定義を解除すると、高価な関数が呼び出されないのですか? または、呼び出しを行ってから、何も追跡しないと判断しますか?

とにかく、それが削除されたとしても、これは C++ マクロよりも劣っています。なぜなら、その大きな醜い呼び出しを C++ での単純な VLOG() 呼び出しのように見せて、パラメーターの評価を回避することはできないからです。また、C++ のように実行時に冗長性を低く定義してオーバーヘッドを回避することもできません。

4

9 に答える 9

9

質問の 1 つに答えるために、Trace.WriteLine がコンパイルされている場合、Trace.WriteLine (またはその兄弟/いとこ) を呼び出すために評価する必要があるすべてのメソッド呼び出しは呼び出されません。したがって、高価なメソッド呼び出しを Trace 呼び出しのパラメーターとして直接配置すると、TRACE シンボルを定義しないと、コンパイル時に削除されます。

実行時に詳細度を変更することに関する他の質問があります。ここでの秘訣は、Trace.WriteLine および同様のメソッドが文字列フォーマット引数として「params object[] args」を取ることです。文字列が実際に発行された場合 (冗長性が十分に高く設定されている場合) にのみ、メソッドはそれらのオブジェクトに対して ToString を呼び出して、それらのオブジェクトから文字列を取得します。したがって、私がよく行うトリックは、完全に組み立てられた文字列ではなくオブジェクトをこれらのメソッドに渡し、文字列の作成を渡すオブジェクトの ToString に残すことです。これにより、実行時のパフォーマンス税は、ログが実際に発生しているときにのみ支払われます。また、アプリを再コンパイルせずに冗長性を自由に変更できます。

于 2009-02-17T05:31:19.410 に答える
2

Conditional(Trace) に関するすべての情報は良好ですが、実際の質問は、Trace 呼び出しを本番コードに残したいが、(通常) 問題が発生しない限り実行時に無効にすることだと思います。

TraceSource を使用している場合 (実行時にコンポーネント レベルでトレースをより細かく制御できるため、Trace を直接呼び出すのではなく、Trace を呼び出す必要があると思います)、次のようなことができます。

if (Component1TraceSource.ShouldTrace(TraceEventType.Verbose))
     OutputExpensiveTraceInformation()

これは、トレース パラメーターを別の関数に分離できることを前提としています (つまり、このコードが含まれる関数のパラメーターに対する高価な操作ではなく、現在のクラスのメンバーにほとんど依存しています)。

このアプローチの利点は、JITer が必要に応じて関数ごとにコンパイルされるためです。「if」が false と評価された場合、関数は呼び出されないだけでなく、JIT も行われません。欠点は、(a) この呼び出しと関数 OutputExpensiveTraceInformation の間でトレース レベルの知識を分けていることです (たとえば、TraceEventType を TraceEventType.Information に変更した場合、それは機能しません。この例では、TraceSource が Verbose レベルのトレースに対して有効になっていない限り、それを呼び出すことさえできます)、(b) 記述するコードが増えます。

これは、C のようなプリプロセッサが役立つと思われるケースです (たとえば、ShouldTrace と最終的な TraceEvent 呼び出しのパラメータが同じであることを確認できるため) が、C# がそうでない理由は理解できます。それを含めます。

TraceEvent に渡すオブジェクトの .ToString メソッドで高価な操作を分離するというアンドリューの提案も良いものです。その場合、たとえば、高価な文字列表現を構築したいオブジェクトを渡す Trace に使用されるオブジェクトを開発し、そのコードを trace オブジェクトの ToString メソッドで行うのではなく分離することができます。パラメーター リストを TraceEvent 呼び出しに追加します (実行時に TraceLevel が有効になっていない場合でも実行されます)。

お役に立てれば。

于 2010-07-15T21:20:06.397 に答える
2

ConditionalAttributeはあなたの親友です。#define が設定されていない場合、呼び出しは完全に削除されます (呼び出しサイトが #if されたかのように)。

編集:誰かがこれをコメントに入れました(ありがとう!)が、主な回答本文で注目に値します:

Trace クラスのすべてのメソッドは Conditional("TRACE") で装飾されています。これをリフレクターを使って見ただけです。

つまり、TRACE が定義されていない場合、Trace.Blah(...expensive...) は完全に消えます。

于 2009-02-17T05:40:11.090 に答える
2

私にとってうまくいった解決策は、シングルトンクラスを使用することです。ロギング機能を公開し、その動作を効率的に制御できます。クラス「AppLogger」を呼び出しましょう。彼女は一例です

public class AppLogger
{
   public void WriteLine(String format, params object[] args)
    {
        if ( LoggingEnabled )
        {
            Console.WriteLine( format, args );
        }
    }
}

上記の例では、Singleton のものは除外されていることに注意してください。チューブの外にはたくさんの良い例があります。ここで興味深いのは、マルチスレッドをサポートする方法です。私はこのようにしました:(簡潔にするために省略されています、はははは)

public static void WriteLine( String format, params object[] args )
{
    if ( TheInstance != null )
    {
        TheInstance.TheCreatingThreadDispatcher.BeginInvoke(  Instance.WriteLine_Signal, format, args );
    }
}

このようにして、任意のスレッドがログに記録でき、メッセージは元の作成スレッドで処理されます。または、ロギング出力を処理するためだけに特別なスレッドを作成することもできます。

于 2009-02-17T05:54:20.773 に答える
1

log4net(http://logging.apache.org/log4net/index.html)のような洗練されたロギングAPIを試しましたか?

于 2009-02-17T07:10:17.160 に答える
1

これらの回答のうちの 2 つ (Andrew Arnott のものと Brian のもの) は、私の質問の一部に答えました。Trace および Debug クラス メソッドに適用される ConditionalAttribute により、TRACE または DEBUG が #undef されている場合、コストのかかるパラメーター評価を含め、メソッドへのすべての呼び出しが削除されます。ありがとう!

2 番目の部分については、コンパイル時ではなく、実行時にすべての呼び出しを完全に削除できるかどうかについて、log4net facで答えを見つけました。彼らによると、起動時に読み取り専用プロパティを設定すると、ランタイムはテストに合格しないすべての呼び出しをコンパイルします! これにより、起動後に変更することはできませんが、それでも問題ありません。コンパイル時に削除するよりはましです。

于 2009-02-18T03:24:10.500 に答える
0

あなたのコメントのために

「その大きな醜い呼び出しを、C++ での単純な VLOG() 呼び出しのように見せることができないため」 - 以下の例のように、using ステートメントを追加できます。

using System.Diagnostics;

....
Trace.WriteLineIf(.....)

私が理解しているように、Trace シンボルを定義解除すると、Trace を含む行が削除されます。

于 2009-02-17T05:14:46.453 に答える
0

必要な副作用がある可能性があるため、高価な呼び出しを呼び出します。

できることは、高価なメソッドを [Conditional("TRACE")] または [Conditional("DEBUG")] 属性で装飾することです。DEBUG または TRACE 定数が定義されていない場合、メソッドは最終的な実行可能ファイルにコンパイルされません。また、高価なメソッドを実行するための呼び出しも行われません。

于 2009-02-17T05:48:05.390 に答える
0

よくわかりませんが、答えは自分で見つけることができます。

非常に高価な関数 ( などThread.Sleep(10000)) にして、呼び出しの時間を計ります。非常に長い時間がかかる場合は、とにかく関数を呼び出しています。

Trace.WriteLineIf()(呼び出しを#if TRACEandでラップ#endifし、ベース比較のために再度テストできます。)

于 2009-02-17T05:17:26.480 に答える