web-dev-qa-db-ja.com

TypeInitializationExceptionの改善(innerExceptionもnull)

イントロ

ユーザーがNLogの構成に誤り(無効なXMLなど)を作成すると、We(NLog)はNLogConfigurationExceptionをスローします。例外には、何が問題なのかという説明が含まれています。

ただし、NLogへの最初の呼び出しが静的フィールド/プロパティからのものである場合、このNLogConfigurationExceptionSystem.TypeInitializationExceptionによって「食べられる」ことがあります。

例えば。ユーザーがこのプログラムを持っている場合:

using System;
using System.Collections.Generic;
using System.Linq;
using NLog;

namespace TypeInitializationExceptionTest
{
    class Program
    {
        //this throws a NLogConfigurationException because of bad config. (like invalid XML)
        private static Logger logger = LogManager.GetCurrentClassLogger();

        static void Main()
        {
            Console.WriteLine("Press any key");
            Console.ReadLine();
        }
    }
}

設定に誤りがあると、NLogは次のようにスローします。

throw new NLogConfigurationException("Exception occurred when loading configuration from " + fileName, exception);

しかし、ユーザーには次のように表示されます。

Throw exception

「例外の詳細をクリップボードにコピーする」:

System.TypeInitializationExceptionが未処理でしたメッセージ:タイプ 'System.TypeInitializationException'の未処理の例外がmscorlib.dllで発生しました追加情報: 'TypeInitializationExceptionTest.Program'の型初期化子が例外をスローしました。

だからメッセージは消えた!

質問

  1. InnerExceptionが表示されないのはなぜですか? (Visual Studio 2013でテスト済み)。
  2. TypeInitializationExceptionに詳細情報を送信できますか?メッセージが好きですか?すでにinnerExceptionを送信しています。
  3. 別の例外を使用できますか、それともExceptionにプロパティがあり、より多くの情報が報告されますか?
  4. ユーザーに(もっと)フィードバックを与える別の方法はありますか?

メモ

編集

私は図書館の管理者であり、図書館の利用者ではないことに注意してください。 呼び出しコードを変更できません!

17
Julian

ここであなたが扱っている根本的な問題を指摘しておきます。あなたはデバッガーのバグと戦っています、それは非常に簡単な回避策を持っています。 [ツール]> [オプション]> [デバッグ]> [一般]を使用し、[管理された互換モードを使用する]チェックボックスをオンにします。また、最も有益なデバッグレポートについては、Just MyCodeのチェックを外してください。

enter image description here

[マイコードのみ]にチェックマークが付いている場合、例外レポートの情報は少なくなりますが、[詳細の表示]リンクをクリックして簡単にドリルダウンできます。

オプション名は不必要に不可解です。 reallyが行うことは、VisualStudioに古いバージョンのデバッグエンジンを使用するように指示することです。 VS2013またはVS2015を使用する人は誰でも、新しいエンジン、おそらくVS2012でこの問題が発生します。また、この問題が以前にNLogで対処されていなかった基本的な理由。

これは非常に優れた回避策ですが、簡単に見つけることはできません。また、プログラマーは特に古いエンジンを使用することを好みません。戻り値のデバッグや64ビットコードのE + Cなどの光沢のある新機能は、古いエンジンではサポートされていません。これが本当にバグなのか、見落としなのか、新しいエンジンの技術的な制限なのかを推測するのは難しいです。これは非常に醜いので、「バグ」というラベルを付けることを躊躇しないでください。connect.Microsoft.comにこれを持って行くことを強くお勧めします。それが修正されると誰もが先を行くでしょう、私は覚えている少なくとも一度はこれに頭をかきました。 [デバッグ]> [Windows]> [例外]> [その時点でチェックされたCLR例外]を使用してドリルダウンしました。

この非常に不幸な振る舞いの回避策は、醜いものになるはずです。プログラムの実行が十分に進行するまで、例外の発生を遅らせる必要があります。私はあなたのコードベースを十分に知りませんが、最初のロギングコマンドがそれを処理する必要があるまで設定の解析を遅らせます。または、例外オブジェクトを保存して、最初のログコマンドにスローします。おそらく簡単です。

11
Hans Passant

私が見る理由は、エントリポイントクラスの型の初期化が失敗したためです。タイプが初期化されていないため、タイプローダーはTypeInitializationExceptionで失敗したタイプについて報告するものがありません。

ただし、ロガーの静的初期化子を他のクラスに変更してから、Entryメソッドでそのクラスを参照する場合。 TypeInitialization例外でInnerExceptionが発生します。

static class TestClass
{
    public static Logger logger = LogManager.GetCurrentClassLogger();  
}

class Program
{            
    static void Main(string[] args)
    {
        var logger = TestClass.logger;
        Console.WriteLine("Press any key");
        Console.ReadLine();
    }
}

TypeInitializationExceptionを報告するためにエントリタイプがロードされたため、InnerExceptionが発生します。

enter image description here

これで、エントリポイントをクリーンに保ち、エントリポイントクラスの静的プロパティの代わりにMain()からアプリケーションをBootstrapするというアイデアが得られることを願っています。

更新1

Lazy<>を利用して、宣言時の構成初期化の実行を回避することもできます。

class Program
{
    private static Lazy<Logger> logger = new Lazy<Logger>(() => LogManager.GetCurrentClassLogger());

    static void Main(string[] args)
    {
        //this will throw TypeInitialization with InnerException as a NLogConfigurationException because of bad config. (like invalid XML)
        logger.Value.Info("Test");
        Console.WriteLine("Press any key");
        Console.ReadLine();
    }
}

または、LogManagerでLazy<>を試してロガーをインスタンス化し、実際に最初のログステートメントが発生したときに構成の初期化が行われるようにします。

アップデート2

NLogのソースコードを分析したところ、すでに実装されているようで、理にかなっています。プロパティのコメントによると、「LogManager.csのプロパティLogManager.ThrowExceptionsで指定されていない限り、NLogは例外をスローしないでください」。

修正-LogFactoryクラスで、プライベートメソッドGetLogger()に初期化ステートメントがあり、これが例外の発生を引き起こしています。プロパティThrowExceptionsのチェックを使用してtrycatchを導入すると、初期化例外を防ぐことができます。

      if (cacheKey.ConcreteType != null)
            {
                try
                {
                    newLogger.Initialize(cacheKey.Name, this.GetConfigurationForLogger(cacheKey.Name, this.Configuration), this);
                }
                catch (Exception ex)
                {
                    if(ThrowExceptions && ex.MustBeRethrown())
                    throw;
                }
            }

また、これらの例外/エラーをどこかに保存しておくと、ThrowExceptionが原因で無視されたためにロガーの初期化が失敗した理由を追跡できます。

6
vendettamit

問題は、クラスが最初に参照されたときに静的初期化が発生することです。 Programでは、Main()メソッドの前でも発生します。したがって、経験則として、静的初期化メソッドで失敗する可能性のあるコードは避けてください。あなたの特定の問題に関しては-代わりに怠惰なアプローチを使用してください:

private static Lazy<Logger> logger = 
  new Lazy<Logger>(() => LogManager.GetCurrentClassLogger());

static void Main() {
  logger.Value.Log(...);
}

したがって、ロガーの初期化は、最初にロガーにアクセスするときに発生します(場合によっては失敗します)。クレイジーな静的コンテキストではありません。

[〜#〜]更新[〜#〜]

ベストプラクティスに固執することは、最終的にはライブラリのユーザーの負担になります。だから私だったらそのままにしておきます。あなたが本当にあなたの側でそれを解決しなければならないならば、しかしいくつかのオプションがあります:

1)例外をスローしないでください-これまで-これはロギングエンジンで有効なアプローチであり、log4netがどのように機能するか-つまり、.

static Logger GetCurrentClassLogger() {
    try {
       var logger = ...; // current implementation
    } catch(Exception e) {
        // let the poor guy now something is wrong - provided he is debugging
        Debug.WriteLine(e);
        // null logger - every single method will do nothing 
        return new NullLogger();
    }
}

2)Loggerクラスの実装に怠惰なアプローチをラップします(Loggerクラスははるかに複雑です。この問題のために、メソッドLogが1つだけで、Loggerインスタンスを構築するのにstring classNameが必要であると仮定しましょう。

class LoggerProxy : Logger {
  private Lazy<Logger> m_Logger;
  // add all arguments you need to construct the logger instance
  public LoggerProxy(string className) {
    m_Logger = new Lazy<Logger>(() => return new Logger(className)); 
  }
  public void Log(string message) {
   m_Logger.Value.Log(message);
  }
}

static Logger GetCurrentClassLogger() {
  var className = GetClassName();
  return new LoggerProxy(className);
}

この問題を取り除くことができます(最初のlogメソッドが呼び出されている間に実際の初期化が行われ、下位互換性のあるアプローチです)。唯一の問題は、別のレイヤーを追加したことです(パフォーマンスが大幅に低下することはないと思いますが、一部のロギングエンジンは実際にマイクロ最適化に取り組んでいます)。

3
Ondrej Svejdar

以前のコメントで、あなたの問題を再現することはできないと言いました。次に、詳細を表示リンクが表示されないポップアップ例外ダイアログでのみを探していたことがわかりました。 。

とにかくInnerExceptionで取得する方法は次のとおりです。なぜなら、間違いなくそこにあるからです。ただし、VisualStudioが一部のレポートを作成しない場合を除きます。理由(おそらく、vendettamitが理解したエントリポイントタイプにあるため)。

したがって、テスターブランチを実行すると、次のダイアログが表示されます。

Exception dialog

また、詳細の表示リンクは表示されません。

例外の詳細をクリップボードにコピーするも特に役に立ちません:

System.TypeInitializationExceptionは処理されませんでした
メッセージ:mscorlib.dllでタイプ「System.TypeInitializationException」の未処理の例外が発生しました
追加情報:「TypeInitializationExceptionTest.Program」の型初期化子が例外をスローしました。

ここで、[〜#〜] ok [〜#〜]ボタンを使用してダイアログを閉じ、Localsデバッグウィンドウに移動します。 。表示される内容は次のとおりです。

Locals view

見る? InnerExceptionは間違いなくあります、そしてあなたはVSの癖を見つけました:-)

3

私が今見ている唯一の解決策は次のとおりです。

静的初期化(フィールド)をtry catchの静的コンストラクターに移動します

2
Julian

System.TypeInitializationException は、クラスの静的メンバーの初期化が正しくないために、常にまたはほとんど常に発生します。

デバッガーでLogManager.GetCurrentClassLogger()を確認する必要があります。エラーはコードのその部分で発生していると確信しています。

//go into LogManager.GetCurrentClassLogger() method
private static Logger logger = LogManager.GetCurrentClassLogger();

また、app.configを再確認し、問題が含まれていないことを確認することをお勧めします。

System.TypeInitializationExceptionは、静的コンストラクターが例外をスローするたび、または静的コンストラクターが例外をスローしたクラスにアクセスしようとするたびにスローされます。

.NETが型をロードするとき、初回を使用する前に、すべての静的フィールドを準備する必要があります。タイプ。初期化にはコードの実行が必要な場合があります。 System.TypeInitializationExceptionを取得するのは、そのコードが失敗したときです。

docs によると

クラス初期化子が型の初期化に失敗すると、TypeInitializationExceptionが作成され、型のクラス初期化子によってスローされた例外への参照が渡されます。 TypeInitializationExceptionのInnerExceptionプロパティは、基になる例外を保持します。 TypeInitializationExceptionは、値0x80131534のHRESULTCOR_E_TYPEINITIALIZATIONを使用します。 TypeInitializationExceptionのインスタンスの初期プロパティ値のリストについては、TypeInitializationExceptionコンストラクターを参照してください。

1)InnerExceptionは表示されません。これは、このタイプの例外では非常に一般的です。 Visual Studioのバージョンは関係ありません("例外アシスタントを有効にする"オプションがオンになっていることを確認してください--Tools> Options>Debugging>General

2)通常、TypeInitializationExceptionは、InnerExceptionを介して表示できる実際の例外を非表示にします。ただし、以下のテスト例は、内部例外情報を入力する方法を示しています。

public class Test {
    static Test() {
        throw new Exception("InnerExc of TypeInitializationExc");
    }
    static void Main(string[] args) {
    }
}

ただし、NLogへの最初の呼び出しが静的フィールド/プロパティからのものである場合、このNLogConfigurationExceptionがSystem.TypeInitializationExceptionによって「食われる」ことがあります。

何も奇妙なことはありません。誰かがどこかでtry catchブロックを見逃しました。

0
isxaker