none
例外発生元の情報を取得したい RRS feed

  • 質問

  • 質問はタイトルの通りです。

    ■クラスA
    Private Sub A_search()
    	・
    	・
    	Dim RecCount As Integer = B.GetRecCount()     ←★★★
    	・
    	・
    End Sub
    
    ■クラスB
    Private Function GetRecCount() As Integer
    	SQLServerに接続しレコード件数を取得
    End Function
    
    ■★★★で例外発生
    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        Server.Transfer("c_error.aspx")
    End Sub
    
    ■c_error.aspx.vb
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Not IsPostBack Then
        	●●●
        End If
    End Sub
    

    ●●●で下記情報をログとして出力したいです。

    ・例外発生元メソッド名(上記の場合:GetRecCount)

    ・例外発生元クラス名(上記の場合:B)

    ・例外発生行番号

    ・原因

    下記①②も試してみましたが取得できませんでした。

    ①
    Dim Proc As StackFrame
    Dim History As New StackTrace(True)
    Dim K As Integer
    
    Dim ClassName As String '呼び出し履歴上のクラス名
    Dim ProcName As String '呼び出し履歴上のプロシージャ名
    Dim SourceFileName As String
    Dim LineNumber As Integer
    
    For K = 0 To History.FrameCount - 1
        'スタックフレームを取得
        Proc = History.GetFrame(K)
    
        'この履歴のクラス名、プロシージャ名等を取得
        ClassName = Proc.GetMethod.ReflectedType.Name
        ProcName = Proc.GetMethod.Name
        SourceFileName = Proc.GetFileName
        LineNumber = Proc.GetFileLineNumber
    Next K
    
    ②
    Dim Err As Exception = Server.GetLastError().InnerException

    方法をご存じの方がいらっしゃいましたら、是非教えてください。

    宜しくお願い致します。

    2012年2月27日 4:39

回答

  • ②の方法でExceptionを取得し、Err.ToStringでお望みの情報を取得できると思います。


    Blog:プログラマーな日々 http://d.hatena.ne.jp/JHashimoto/

    • 回答としてマーク hana0101 2012年3月5日 0:30
    2012年2月27日 6:25
  • その情報を何に利用したいですか?発生したメソッドのメソッド名だけだとあまり意味がないように思えます。

    例えば、例外の発生元が.NET Framework内部のメソッドだったり、回り回った結果たまたまその例外だったりしたらどうしますか?

    System.String.SubString で NullReferenceが発生したという情報よりも、SubStringがどういった経路でよばれているかがわかったほうがいいんじゃないですか?


    大元の質問についてですが、なんらかの例外が発生した際に、最後にその原因となるメソッドを呼び出したユーザーコードのメソッドと行番号だけを取得したいということですが、そういったメソッドは無いと思います。もしやりたいなら、頑張ってスタックトレースを解析する、、、のかな?

    あと、行番号はPDBが生成されていない場合は取得できないことにも注意したほうがいいです。

    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月27日 7:47
  • Application_Error の中で Server.GetLastError().InnerException から例外を取り出せるはずですので、
    この例外をもとに StackTrace オブジェクトを構築すれば、色々と取り出せるのではないかと思います。
    例えば、
    Dim Err As Exception = Server.GetLastError().InnerException
    Dim ErrMethod As System.Reflection.MethodInfo = New StackTrace(Err).GetFrame(0).GetMethod()

    とすると、ErrMethod 変数を介してご期待の情報を持ってこれそうに思います。

    # ご期待の情報が有用な情報であるかは別として。
    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月28日 8:27
  • かるあさんへのレスには、

    > ②の方法だと、
    > クラスA・A_search・○行目
    > クラスB・GetRecCount・○行目
    > という最初の目的であったものも取得できる

    とありますが、私へのレスには、

    > 今回の目的のクラスB、GetRecCountは取得できませんでした。

    とあります。矛盾してないでしょうか?

    何にせよ、以下のページにある ExceptionUtility の情報があれば
    よさそうに思えますが、どうでしょうか?

    エラー ハンドラーの完全なコード例
    http://msdn.microsoft.com/ja-jp/library/bb397417.aspx

    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月28日 13:13
  • 質問はタイトルの通りです。

     え?スタック トレース調べれば良いじゃない。

     そうではなく、本文では「なぜタイトルの様な質問をしたいか」と言うことを説明して頂けないでしょうか。
    今回のケースなら、次のようになるかと思います。

    質問の背景は、次の通りです。
     ASP.NET で、例外が発生した場合に、全ての例外を error.aspx で表示し、情報を集約しようとしています。【重要な情報1:質問の背景】この目的のため、次のようなコードを書きました。【重要な情報2:実際に動作させたコード】

    ■クラスB 例外が発生するクラス
    Private Function GetRecCount() As Integer
        SQLServerに接続しレコード件数を取得中に例外が発生する場合を想定【重要な情報3:質問している現象が発生する条件】
    End Function
    
    ■ Global.asax
    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        Server.Transfer("c_error.aspx")
    End Sub
    
    ■c_error.aspx.vb 例外情報を表示するページ
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Not IsPostBack Then
            ここに書くべきコードについて【重要な情報4:質問の主題】
        End If
    End Sub
    

     c_error.aspx.vb にて、次の情報をログ出力させたいと考えています。

    • 例外発生元メソッド名(上記の場合:GetRecCount)
    • 例外発生元クラス名(上記の場合:B)
    • 例外発生行番号
    • 原因

     やったこととして、次のようなコードを試しましたが、希望する情報を得られませんでした。【重要な情報5:実行したこと】

    ①
    Dim Proc As StackFrame
    Dim History As New StackTrace(True)
    Dim K As Integer
    
    Dim ClassName As String '呼び出し履歴上のクラス名
    Dim ProcName As String '呼び出し履歴上のプロシージャ名
    Dim SourceFileName As String
    Dim LineNumber As Integer
    
    For K = 0 To History.FrameCount - 1
        'スタックフレームを取得
        Proc = History.GetFrame(K)
    
        'この履歴のクラス名、プロシージャ名等を取得
        ClassName = Proc.GetMethod.ReflectedType.Name
        ProcName = Proc.GetMethod.Name
        SourceFileName = Proc.GetFileName
        LineNumber = Proc.GetFileLineNumber
    Next K
    
    ②
    Dim Err As Exception = Server.GetLastError().InnerException
    

    この様にしたところ、①では期待するクラスB、GetRecCount は表示されませんでした。②では余計な情報、クラス B、GetRecCount メソッド以外の情報も出てしまいます。【重要な情報6:実行したことに対して期待したこと】【重要な情報7:実行して得られた結果】

     「どういう目的があって質問に至った」というのは、重要な情報です。皆さん、何らかの調査をして、この方向で行こうと決めて、その途中で行き詰まって質問に至っています。ところが、話を聞いていると、進めている方向が間違っている事があります。この場合、目的を聞き出すまでの時間が無駄になります。(回答者は困りません。でも、質問者さんは、早く解決したいのでは?)

     例えば、今回、c_error.aspx の画面は、“誰に”見せることを目的としているのでしょうか。これが“エンド ユーザー”であれば、この様な機構は不要…というか、実装してはいけないかもしれません。.NET では、特に ASP.NET プロジェクトでは、例外メッセージをエンド ユーザーに見せることは、控えるように言われています。例外メッセージは、開発者向けのメッセージです。つまり、想定される例外発生状況は、開発中にすべて塞ぎ、実行時には本当に例外的な状況でのみ、例外が発生するようにします。(参照:とあるコンサルタントのつぶやき「.NET の例外処理 Part. 4」このため、例外情報はむやみにエンドユーザに見せてはいけません。特に、そこそこ知識を持った開発者が例外情報を読むと、アプリケーション内部構造に関する情報が漏えいすることになり、セキュリティ上のリスクにつながります。)
     今回は「SQLServer に接続しレコード件数を取得」ということですが、この作業の中でどんな例外が発生するでしょうか。ここでエンド ユーザーに通達しなければならないのは、「接続できなかった」ということぐらいではないでしょうか。
    では何故接続できなかったのか。「DB サーバーが起動していない」「Web サーバーから DB サーバーまでのネットワーク回線が物理的に切れている」などが想定されます。エンド ユーザーに見せるのは、こういった、エンド ユーザーが何とかできる問題と、それに対する解決方法です。

     エンド ユーザーから開発元に送信するログ ファイルに保存するということであれば、Application_Error メソッドで行うと良いでしょう。ここなら、ひとつ上が発生したメソッドではないでしょうか。そして、今回の質問では、ここでセッション変数なりにしまってしまうという手もあります。


     なお、①の方法では、Server.Transfer をしたときにコンテキストが変わっています。その為に以前のコンテキストが取れません。(参照:「HttpServerUtility.Transer メソッド(String)」Transfer は End を呼び出します。これは完了時に ThreadAbortException 例外をスローします。)Redirect では一旦クライアントに制御が返り、もう一度リクエストが発行されます。Transfer はサーバー内でコンテキストの変更を行います。
     ②の方法では、Exception が取れるなら、そこに StackTrace が入っているので、その StackTrace を①のようにして取り出せば良いのではないかと思います。もっとも、何番目に必要な情報があるのか、わかりませんが(試さずに言いますが、InnerException でなくても良いのでは?)。


    Jitta@わんくま同盟

    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月29日 14:04

すべての返信

  • ②の方法でExceptionを取得し、Err.ToStringでお望みの情報を取得できると思います。


    Blog:プログラマーな日々 http://d.hatena.ne.jp/JHashimoto/

    • 回答としてマーク hana0101 2012年3月5日 0:30
    2012年2月27日 6:25
  • ②の方法でExceptionを取得し、Err.ToStringでお望みの情報を取得できると思います。


    Blog:プログラマーな日々 http://d.hatena.ne.jp/JHashimoto/

    J.Hashimoto様、ありがとうございます。

    Err.ToStringだと余計な情報まで取得してしまうのですが、

    目的のものだけ取得するにはどうしたら良いでしょうか?(クラス名、行番号等)

    2012年2月27日 6:59
  • その情報を何に利用したいですか?発生したメソッドのメソッド名だけだとあまり意味がないように思えます。

    例えば、例外の発生元が.NET Framework内部のメソッドだったり、回り回った結果たまたまその例外だったりしたらどうしますか?

    System.String.SubString で NullReferenceが発生したという情報よりも、SubStringがどういった経路でよばれているかがわかったほうがいいんじゃないですか?


    大元の質問についてですが、なんらかの例外が発生した際に、最後にその原因となるメソッドを呼び出したユーザーコードのメソッドと行番号だけを取得したいということですが、そういったメソッドは無いと思います。もしやりたいなら、頑張ってスタックトレースを解析する、、、のかな?

    あと、行番号はPDBが生成されていない場合は取得できないことにも注意したほうがいいです。

    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月27日 7:47
  • > 下記①②も試してみましたが取得できませんでした。

    ②は Exception を取得することはできたが、使えなかったというこ
    とですよね。

    ①を試した結果はどのようになったのですか?

    2012年2月27日 13:52
  • その情報を何に利用したいですか?発生したメソッドのメソッド名だけだとあまり意味がないように思えます。

    例えば、例外の発生元が.NET Framework内部のメソッドだったり、回り回った結果たまたまその例外だったりしたらどうしますか?

    System.String.SubString で NullReferenceが発生したという情報よりも、SubStringがどういった経路でよばれているかがわかったほうがいいんじゃないですか?


    大元の質問についてですが、なんらかの例外が発生した際に、最後にその原因となるメソッドを呼び出したユーザーコードのメソッドと行番号だけを取得したいということですが、そういったメソッドは無いと思います。もしやりたいなら、頑張ってスタックトレースを解析する、、、のかな?

    あと、行番号はPDBが生成されていない場合は取得できないことにも注意したほうがいいです。

    かるあ様、ありがとうございます。

    確かにそうですね。どのような流れで例外が起きたのか、それをログとして出力した方が良さそうですね。

    ②の方法だと、

    クラスA・A_search・○行目

    クラスB・GetRecCount・○行目

    という最初の目的であったものも取得できるので、その他の情報も含めてログ出力した方がいいかもです。

    2012年2月28日 0:04
  • > 下記①②も試してみましたが取得できませんでした。

    ②は Exception を取得することはできたが、使えなかったというこ
    とですよね。

    ①を試した結果はどのようになったのですか?

    SurferOnWww様、ありがとうございます。

    今回の目的のクラスB、GetRecCountは取得できませんでした。

    c_error.aspx.vb、global.asaxが取得されました。

    2012年2月28日 0:08
  • Application_Error の中で Server.GetLastError().InnerException から例外を取り出せるはずですので、
    この例外をもとに StackTrace オブジェクトを構築すれば、色々と取り出せるのではないかと思います。
    例えば、
    Dim Err As Exception = Server.GetLastError().InnerException
    Dim ErrMethod As System.Reflection.MethodInfo = New StackTrace(Err).GetFrame(0).GetMethod()

    とすると、ErrMethod 変数を介してご期待の情報を持ってこれそうに思います。

    # ご期待の情報が有用な情報であるかは別として。
    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月28日 8:27
  • かるあさんへのレスには、

    > ②の方法だと、
    > クラスA・A_search・○行目
    > クラスB・GetRecCount・○行目
    > という最初の目的であったものも取得できる

    とありますが、私へのレスには、

    > 今回の目的のクラスB、GetRecCountは取得できませんでした。

    とあります。矛盾してないでしょうか?

    何にせよ、以下のページにある ExceptionUtility の情報があれば
    よさそうに思えますが、どうでしょうか?

    エラー ハンドラーの完全なコード例
    http://msdn.microsoft.com/ja-jp/library/bb397417.aspx

    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月28日 13:13
  • 質問はタイトルの通りです。

     え?スタック トレース調べれば良いじゃない。

     そうではなく、本文では「なぜタイトルの様な質問をしたいか」と言うことを説明して頂けないでしょうか。
    今回のケースなら、次のようになるかと思います。

    質問の背景は、次の通りです。
     ASP.NET で、例外が発生した場合に、全ての例外を error.aspx で表示し、情報を集約しようとしています。【重要な情報1:質問の背景】この目的のため、次のようなコードを書きました。【重要な情報2:実際に動作させたコード】

    ■クラスB 例外が発生するクラス
    Private Function GetRecCount() As Integer
        SQLServerに接続しレコード件数を取得中に例外が発生する場合を想定【重要な情報3:質問している現象が発生する条件】
    End Function
    
    ■ Global.asax
    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        Server.Transfer("c_error.aspx")
    End Sub
    
    ■c_error.aspx.vb 例外情報を表示するページ
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Not IsPostBack Then
            ここに書くべきコードについて【重要な情報4:質問の主題】
        End If
    End Sub
    

     c_error.aspx.vb にて、次の情報をログ出力させたいと考えています。

    • 例外発生元メソッド名(上記の場合:GetRecCount)
    • 例外発生元クラス名(上記の場合:B)
    • 例外発生行番号
    • 原因

     やったこととして、次のようなコードを試しましたが、希望する情報を得られませんでした。【重要な情報5:実行したこと】

    ①
    Dim Proc As StackFrame
    Dim History As New StackTrace(True)
    Dim K As Integer
    
    Dim ClassName As String '呼び出し履歴上のクラス名
    Dim ProcName As String '呼び出し履歴上のプロシージャ名
    Dim SourceFileName As String
    Dim LineNumber As Integer
    
    For K = 0 To History.FrameCount - 1
        'スタックフレームを取得
        Proc = History.GetFrame(K)
    
        'この履歴のクラス名、プロシージャ名等を取得
        ClassName = Proc.GetMethod.ReflectedType.Name
        ProcName = Proc.GetMethod.Name
        SourceFileName = Proc.GetFileName
        LineNumber = Proc.GetFileLineNumber
    Next K
    
    ②
    Dim Err As Exception = Server.GetLastError().InnerException
    

    この様にしたところ、①では期待するクラスB、GetRecCount は表示されませんでした。②では余計な情報、クラス B、GetRecCount メソッド以外の情報も出てしまいます。【重要な情報6:実行したことに対して期待したこと】【重要な情報7:実行して得られた結果】

     「どういう目的があって質問に至った」というのは、重要な情報です。皆さん、何らかの調査をして、この方向で行こうと決めて、その途中で行き詰まって質問に至っています。ところが、話を聞いていると、進めている方向が間違っている事があります。この場合、目的を聞き出すまでの時間が無駄になります。(回答者は困りません。でも、質問者さんは、早く解決したいのでは?)

     例えば、今回、c_error.aspx の画面は、“誰に”見せることを目的としているのでしょうか。これが“エンド ユーザー”であれば、この様な機構は不要…というか、実装してはいけないかもしれません。.NET では、特に ASP.NET プロジェクトでは、例外メッセージをエンド ユーザーに見せることは、控えるように言われています。例外メッセージは、開発者向けのメッセージです。つまり、想定される例外発生状況は、開発中にすべて塞ぎ、実行時には本当に例外的な状況でのみ、例外が発生するようにします。(参照:とあるコンサルタントのつぶやき「.NET の例外処理 Part. 4」このため、例外情報はむやみにエンドユーザに見せてはいけません。特に、そこそこ知識を持った開発者が例外情報を読むと、アプリケーション内部構造に関する情報が漏えいすることになり、セキュリティ上のリスクにつながります。)
     今回は「SQLServer に接続しレコード件数を取得」ということですが、この作業の中でどんな例外が発生するでしょうか。ここでエンド ユーザーに通達しなければならないのは、「接続できなかった」ということぐらいではないでしょうか。
    では何故接続できなかったのか。「DB サーバーが起動していない」「Web サーバーから DB サーバーまでのネットワーク回線が物理的に切れている」などが想定されます。エンド ユーザーに見せるのは、こういった、エンド ユーザーが何とかできる問題と、それに対する解決方法です。

     エンド ユーザーから開発元に送信するログ ファイルに保存するということであれば、Application_Error メソッドで行うと良いでしょう。ここなら、ひとつ上が発生したメソッドではないでしょうか。そして、今回の質問では、ここでセッション変数なりにしまってしまうという手もあります。


     なお、①の方法では、Server.Transfer をしたときにコンテキストが変わっています。その為に以前のコンテキストが取れません。(参照:「HttpServerUtility.Transer メソッド(String)」Transfer は End を呼び出します。これは完了時に ThreadAbortException 例外をスローします。)Redirect では一旦クライアントに制御が返り、もう一度リクエストが発行されます。Transfer はサーバー内でコンテキストの変更を行います。
     ②の方法では、Exception が取れるなら、そこに StackTrace が入っているので、その StackTrace を①のようにして取り出せば良いのではないかと思います。もっとも、何番目に必要な情報があるのか、わかりませんが(試さずに言いますが、InnerException でなくても良いのでは?)。


    Jitta@わんくま同盟

    • 回答としてマーク hana0101 2012年3月5日 0:31
    2012年2月29日 14:04
  • totojo様、ありがとうございます。

    教えて頂いた方法では取得できませんでした。

    アドバイスありがとうござました。

    2012年3月1日 2:11
  • SurferOnWww様、ありがとうございます。

    私の「今回の目的のクラスB、GetRecCountは取得できませんでした。」という返事は

     ①を試した結果はどのようになったのですか?

    この質問に対してのものでした。

    わかりづらくて申し訳ありません。

    教えて頂いたページをこれからじっくりと読んでみます。

    2012年3月1日 2:15
  • 教えて頂いた方法では取得できませんでした。


    うまくいかなかった時に、

      どのようにうまくいかなかったか?
      自分の期待値とどのようにずれているか?

    を明らかにするのが、問題解決への近道だと思います。
    2012年3月1日 2:23
  • Jitta様、ありがとうございます。

    私が伝えたかった事を、詳しくキレイにまとめてくださり、ありがとうございます。

    c_error.aspx は例外情報を表示するのではなく、

    「障害が発生しました。~課までご連絡ください」というような

    ユーザーにエラーが起きている事をわからせる為のページです。

    今回私がしたい事は、例外が発生した場合に

    ①例外内容をログとして出力する

    ②ユーザーには独自のエラーページを表示する

    という事です。

    Application_Error メソッドでの実行も試してみます。


    2012年3月1日 2:28
  • Application_Errorで②を使用したいと思います。

    皆様、ありがとうございました。


    2012年3月5日 0:35