kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

500 – Internal Server Error – One ASP.NET Advent Calender


というわけで、二日続けての Advent Calender、今回は One ASP.NET です。

というわけで、タイトルの事についてゴニョゴニョ語ってみようと思います。タイトルからして嫌なものを題材にしてって感じですが、クリスマスに向けてホノボノし続けてるとたまにホラー映画とか見たくなるじゃないですか、そういうニーズにこたえる記事でございます。

ASP.NET での Web サイトやアプリケーションを皆さんはどのように監視したりしていますか?

  1. ping や一定期間ごとの get リクエストでの死活監視
    これは最低限の監視ですね、サーバやインフラが生きてないんじゃお話にならないでしょう。
  2. 定期的なWebアクセスログやシステムイベントログの収集と分析
    システムイベントログを取ってるとアプリケーションのエラーとかの情報はおおよそ良い感じに集まるので、これをベースに監視や管理を行うのはいいことですね。Web アクセスログはうまく使えば応答時間の変動等も監視できますね。
  3. そもそもやって無いとか
    レンタルサーバにおいていてレンタルサーバ屋さん任せ、自分は特に監視や管理をしないよ!これもありっちゃありな話です。
  4. ASP.NET の監視のAPI使ってるぜ!
    これは珍しいんじゃないかな?っていう事で今回の記事になります。

System.Web.Management 名前空間

ASP.NETなら当然に参照されている System.Web アセンブリに System.Web.Management 名前空間があります。この名前空間で用意されているものが今回の題材です。MSDN でのリファレンスはこちら

Internal Server Error !

さて、 Internal Server Error が発生したら何が起こるでしょう。 Page Error イベントハンドラが起動されたり Application オブジェクトの Error メソッドが呼び出されるとかはまぁご存じのことでしょう。

このエラーの発生時には様々な事をやりたいと思うでしょう、担当を叩き起こしたり、お客への詫び状を書いたり、再発の防止策をまとめたりはさておき、DBサーバは生きてるのか確認したりとか、いろんな確認をしてどこがどんな原因で落ちたのか「ほらバグだ、さあ直せ」「ほらバグだ、さあ直せ」「ほらバグだ、さあ直せ」と踊り狂う営業をどうやって棺桶に入れるかとか色々考えることや調べること、諸々があります。

さて、まっとうに商売しているWeb屋さんなら複数のアプリケーションをもっていたり、複数の顧客のサイトを運営していたりする事はよくある事でGlobal.asax.cs なんてふつうは顧客向けカスタムの巣窟だったりしますよね。

でも、監視管理は自分達の監視管理の方針に揃えたいですよね。

顧客カスタムの巣窟なGlobal.asax には会社として揃えたい物は入れたくない、そうその通り。かといって Application_Error でしか捕まえられないじゃない!

困った結果として HttpApplication と MvcApplication の間に挟まれる中間クラスを書いたりしようにもこの辺は各アプリケーションが自分たちの実装の為に入れ替えてるのがふつうだったりしてアプリケーションの用意するHttpApplication 実装の下には入りづらいけど上に入れるのは基底クラス違いでいくつも書かないとアレとか色々な問題が起こりすぎて泣けてくるのがふつうだったりするでしょう。

能書きはおいといて実装編

さて、Application_Error でない場所で Internal Server Error をトラップしてみましょう。

会社としての管理ポリシーを実装するためのクラスライブラリを作成します。クラスライブラリでは System.Web と System.Configuration を参照設定し、 WebEventProvider の派生クラスを作ります。

image

using System.Web.Management;

namespace EnterpriseManagementPolicy
{
    public class EnterpriseWebEventProvider : WebEventProvider
    {
        public override void ProcessEvent(WebBaseEvent raisedEvent)
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                System.Diagnostics.Debugger.Break();
            }
        }

        public override void Shutdown()
        {
        }

        public override void Flush()
        {
        }
    }
}

Webアプリケーションからクラスライブラリを参照し、web.config の system.web 配下に以下を記述します。

  <system.web>
    <healthMonitoring>
      <providers>
        <add name="Enterprise" type="EnterpriseManagementPolicy.EnterpriseWebEventProvider"/>
      </providers>
      <rules>
        <add name="RouteErrorToEnterprise" eventName="All Errors" provider="Enterprise"/>
      </rules>
    </healthMonitoring>

単純に Home の Index で ApplicationException を throw してみましょう。

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "ASP.NET MVC アプリケーションを簡単に始めるには、このテンプレートを変更してください。";

            throw new ApplicationException("落ちてみるよ!");

            return View();
        }

WebEventProvider の実装コードではデバッガがアタッチしていればブレークするようにしているので、Visual Studio からデバッグ実行するとブレークポイント処理に飛び込みます。

imageimage

やったね!

クイックウォッチで raisedEvent の中身を表示させたのが以下、

image

WebRequestErrorEvent に as キャストしてしまえば例外そのものや、それにまつわるスタックトレース、RequestInformation 配下にはアクセスされたURLまで入っているし、発生日時等一通りの情報が含まれていますのが見て取れますね。

この実装では web.config の修正や参照設定は行いましたがアプリケーションの実装には一切の変更はありませんので、顧客ニーズに応じた処理な global.asax には一切の変更は要らないですし、GACに管理ポリシーを実装するアセンブリを入れて Machine.config/ApplicationHost.config で定義してしまえば実際には web.config の修正も要りません。

http://500error.local/ みたいなローカルイントラネットアプリに HTTP-POST で必要な情報を送り込めば既知バグか判断してくれたり、既知バグだった場合にプライオリティを上げたり、既知バグでなかったら TFS にバグを起票してくれたり、例外の種類がDBサーバ死んでる的なものは DB サーバのチェックコマンドを投げてくれたり色々と自動化のネタが思いつきますね。

既存のWebEventProvider

エラーをメール通知したりとか、一般的な事を何にも書かなかったのはワザとで、エラーのメール通知は標準で存在する WebEventProvider 派生に実装済みです。

メールの送信は以下

SimpleMailWebEventProvider

TemplatedMailWebEventProvider

SQL Server にイベントを書き込むのは以下

SqlWebEventProvider

SQL Server にイベントを書きこむためには aspnet_regsql コマンドで用意されたデータベーステーブルが必要です。

実際に SQL Server にWebEventを書きこむのはどうかと思っていたりしています。(DBがあっぷあっぷしてる時のエラーをDBに書けるかという難問を解けるかですね)

OS のイベントログに書き込むのは以下

EventLogWebEventProvider

エラー時等にOSのイベントログが書かれるのは実際にはこれが動いているからですね。

WMIイベントの発生もできます。

WmiWebEventProvider

そしてIISのトレースインフラに書き込むのが以下です。

IisTraceWebEventProvider

開発中等で見るIISの Failed Request Trace に出ているものはこれが中継しています。

そして、System.Diagnostics.Trace に渡すのが以下

TraceWebEventProvider

ASP.NET のトレースを有効にしていれば、ASP.NET の黄色いアレにイベントが出る事になります。(Trace.axd にも)

カスタムすればどこにでも何とでもなりますけど、標準実装でできる事の範囲内では標準使いましょう。

(Azureでインスタンス飛ぶと遅延転送されるログが消えるのがまずいって人は重要なものを選定してストレージに流せばいいんじゃないかな?)

カスタムイベント

さて、WebEventProvider はイベントをどっかに持っていく物だったりしますが、WebEvent そのものもカスタム定義をする事ができます。

アプリケーションでカスタムイベントを使う事はアプリケーションの要件によりますが、たとえばECサイトとかを作っている場合、お金と商品のやり取りに関するデータだけは絶対に失いたくない物だったりしますよね。こういう物をカスタムイベントを使って WebEvent を流して、 SqlWebEventProvider とカスタムななんかしらのストレージに流す仕組みを作ると、それだけで絶対に失いたくない物のロギングが二重化されます。そのログ2通りからDB状態の整合を取るツールを作っておけば、何か起こった時の命綱として安心感をもたらしてくれる事でしょう。

逆に商品の在庫切れのようなものをWebEventを流しておけば、標準のメール通知のWebEventProviderで管理者に通知する事や、カスタムのWebEventProviderによって商品に応じた担当者に向けた通知を発行するなんて事も可能な形になります。えてしてこういう便利機能をアプリケーションに積み込んでいくことが実際の顧客向けのオンライン性能の制約になったりしますのでWebEventの発行とWebEventProviderでのイベント処理でステージが分離される事のメリットは結構あったりします。

んで、カスタムな WebEvent の実装方法です。 WebBaseEvent を派生するクラスを作って必要なメソッドを実装するだけです。リファレンスの最後に実装例も載ってますね。

イベントの発行は該当クラスを new して Raise を呼ぶだけです、カスタム WebEvent の発行には信頼レベルが Midium が必要な点だけは注意が必要です。

F5アタックに対する考慮

カスタムなWebEventProvider 、WebEventを実装する場合や既存のWebEventを何らかのWebEventProviderに接続する前にはF5アタックのような性能飽和攻撃(サービス拒否攻撃)に対して考慮の必要があるでしょう。未認証ユーザーからのリクエスト等を無駄に詳細なロギングをしようとするとあっという間に性能が飽和してアプリケーションの目的とする動作が実行できなくなったりします。認証ユーザーに対してでも同時ユーザー数が十分に多い場合等はアプリケーションの目的サービスの妨げとならないように性能に気を配った実装をする必要があるかもしれません。

まとめ

アプリケーションの Error 処理で Global.asax.cs で Application_Error を書くのはあんまりお勧めできないです。

ロギングしたいなら既存の WebEventProvider でできますので web.config でルール設定すればいいんです。それ以外のカスタムエラー画面出すようにするとかも web.config での設定の範囲でできる事だったりします。それ以上のエラー処理ってあんまり無かったりしませんですか?ログの体裁をむにゃむにゃしたいとかならカスタムなWebEventProvider 作ればいいじゃないかとかとか、log4net に出したいとかも、log4net に出す為の WebEventProvider 作ればよろし。失って困る物は WebEvent 発行して複数の WebEventProviderでログすればまず困らない。

とかいう私も数年前まで Application_Error になんかつじつま合わせを書いていた気がします、無知識だったゆえって感じですので今後は Application_Error を書くことは殆ど無いんじゃないかなって感じです。

Application_Error にエラー処理を書いたら負けかなと思っているとまでは言わないですけど、いろんなつじつま合わせなエラー処理が必要なアプリケーションは設計おかしくないか?という観点で見直してみて頂きたく。

この記事に書いたことのほとんどは、MSDNリファレンスの以下から参照できますので、エラー処理とか壮大なものを設計する前に読んだ方がよさそうだと思った人はどうぞ。

ASP.NET Health Monitoring の概要

では、ハッピーなクリスマスを!

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。