65
64

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails で捕捉されない例外が発生したらメールを送る

Last updated at Posted at 2012-12-26

Rails Advent Calendar 12 日目です。

特になにもしていないと、運用中の Rails アプリケーションで例外が起こっても、気づくのは困難です。

New Relic とか使うとそのあたりは解決しそうな気もしますが (あんまり使ってないので知らない)、簡単に導入できるものとして exception_notification を紹介します。

exception_notification

exception_notification は Rack ミドルウェアで、捕捉されない例外が起こったときに、あらかじめ設定したメールアドレスにメールを送信します。

メール送信後は同じ例外をもう一度 raise するので、そのあとの処理に影響を及ぼすことはありません。

導入

歴史的経緯により notifi__cation__ の箇所と notifi__er__ の箇所があるので注意。

gem 入れて...

# Gemfile
gem "exception_notification"

config/initializers 下に設定ファイルをつくります。

# config/initializers/exception_notification.rb
Rails.application.config.middleware.use(
  ExceptionNotifier,
  :email_prefix => "[アプリケーション名] ",
  :sender_address => %{"送信者名" <送信元メールアドレス>},
  :exception_recipients => %w{送信先メールアドレス}
)

メールサーバの設定は ActionMailer のものをそのまま使うので、ActionMailer が設定されていれば、これで完了です。

ほかにもいろいろ設定できるようですが、そのへんは README.md 参照してください。

サンプル

実際に例外が起きると下記のようなメールが送られてきます。

例外の種類とメッセージ、リクエスト、セッション、Rack の env、バックトレースが含まれています。

Date: Wed, 12 Sep 2012 20:24:33 +0900
From: Exception Notifier <exception@example.com>
To: admin@example.com
Message-ID: <505070f19a852_55923fc4508c93b8729f@Foobar.local.mail>
Subject: [Todo app] home#show (RuntimeError) "Unexpected Error"
Mime-Version: 1.0
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

A RuntimeError occurred in home#show:

  Unexpected Error
  app/controllers/home_controller.rb:3:in `show'

-------------------------------
Request:
-------------------------------

  * URL       : http://localhost:3000/
  * IP address: 127.0.0.1
  * Parameters: {"controller"=>"home", "action"=>"show"}
  * Rails root: /Users/foo/todo

-------------------------------
Session:
-------------------------------

  * session id: "f59bf3b7aa7c9ace32c939766e32d656"
  * data: {"session_id"=>"f59bf3b7aa7c9ace32c939766e32d656",
   "_csrf_token"=>"no97EC4mADf02hVV3OfR+XqBl73Fk4Tp8jBegWnlwWY="}

-------------------------------
Environment:
-------------------------------

  * GATEWAY_INTERFACE                              : CGI/1.1
  * HTTP_ACCEPT                                    : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  * HTTP_ACCEPT_CHARSET                            : Shift_JIS,utf-8;q=0.7,*;q=0.3
  * HTTP_ACCEPT_ENCODING                           : gzip,deflate,sdch
  * HTTP_ACCEPT_LANGUAGE                           : ja,en-US;q=0.8,en;q=0.6
  * HTTP_CACHE_CONTROL                             : max-age=0
  * HTTP_CONNECTION                                : keep-alive
  * HTTP_COOKIE                                    : _todo_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY1OWJmM2I3YWE3YzlhY2UzMmM5Mzk3NjZlMzJkNjU2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5vOTdFQzRtQURmMDJoVlYzT2ZSK1hxQmw3M0ZrNFRwOGpCZWdXbmx3V1k9BjsARg%3D%3D--c34c5fc6de928cde391cccd2b710547c7aab1d06
  * HTTP_HOST                                      : localhost:3000
  * HTTP_IF_NONE_MATCH                             : "132bbc1cdcfdacc705ba8346e527d0bb"
  * HTTP_USER_AGENT                                : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1
  * HTTP_VERSION                                   : HTTP/1.1
  * ORIGINAL_FULLPATH                              : /
  * PATH_INFO                                      : /
  * QUERY_STRING                                   : 
  * REMOTE_ADDR                                    : 127.0.0.1
  * REMOTE_HOST                                    : localhost
  * REQUEST_METHOD                                 : GET
  * REQUEST_PATH                                   : /
  * REQUEST_URI                                    : http://localhost:3000/
  * SCRIPT_NAME                                    : 
  * SERVER_NAME                                    : localhost
  * SERVER_PORT                                    : 3000
  * SERVER_PROTOCOL                                : HTTP/1.1
  * SERVER_SOFTWARE                                : WEBrick/1.3.1 (Ruby/1.9.2/2012-04-20)
  * action_controller.instance                     : home#show
  * action_dispatch.backtrace_cleaner              : #<Rails::BacktraceCleaner:0x007f88a48a3d40>
  * action_dispatch.cookies                        : #<ActionDispatch::Cookies::CookieJar:0x007f88a1171430>
  * action_dispatch.logger                         : #<ActiveSupport::TaggedLogging:0x007f88a2da9508>
  * action_dispatch.parameter_filter               : [:password, /RAW_POST_DATA/]
  * action_dispatch.remote_ip                      : 127.0.0.1
  * action_dispatch.request.content_type           : 
  * action_dispatch.request.formats                : [text/html]
  * action_dispatch.request.parameters             : {"controller"=>"home", "action"=>"show"}
  * action_dispatch.request.path_parameters        : {:controller=>"home", :action=>"show"}
  * action_dispatch.request.query_parameters       : {}
  * action_dispatch.request.request_parameters     : {}
  * action_dispatch.request.unsigned_session_cookie: {"session_id"=>"f59bf3b7aa7c9ace32c939766e32d656", "_csrf_token"=>"no97EC4mADf02hVV3OfR+XqBl73Fk4Tp8jBegWnlwWY="}
  * action_dispatch.request_id                     : aae2c8b270a34ce9323289f38e1ff41e
  * action_dispatch.routes                         : #<ActionDispatch::Routing::RouteSet:0x007f88a15a50d8>
  * action_dispatch.secret_token                   : 609b10fdec535f80b97fed445f930500b43d5e5a6b5212f731e09e4b5c6fbfe99c7350f64875e004706e318cc5fe032258af1596d33f303b7b3cb9ea0958c127
  * action_dispatch.show_detailed_exceptions       : true
  * action_dispatch.show_exceptions                : true
  * exception_notifier.options                     : {:sender_address=>"\"Exception Notifier\" <exception@example.com>", :exception_recipients=>["admin@example.com"], :email_prefix=>"[Todo app] ", :sections=>["request", "session", "environment", "backtrace"], :ignore_exceptions=>[AbstractController::ActionNotFound, ActionController::RoutingError]}
  * rack.errors                                    : #<IO:0x007f88a08718a8>
  * rack.input                                     : #<StringIO:0x007f88a1183798>
  * rack.multiprocess                              : false
  * rack.multithread                               : false
  * rack.request.cookie_hash                       : {"_todo_session"=>"BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY1OWJmM2I3YWE3YzlhY2UzMmM5Mzk3NjZlMzJkNjU2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5vOTdFQzRtQURmMDJoVlYzT2ZSK1hxQmw3M0ZrNFRwOGpCZWdXbmx3V1k9BjsARg==--c34c5fc6de928cde391cccd2b710547c7aab1d06"}
  * rack.request.cookie_string                     : _todo_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY1OWJmM2I3YWE3YzlhY2UzMmM5Mzk3NjZlMzJkNjU2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5vOTdFQzRtQURmMDJoVlYzT2ZSK1hxQmw3M0ZrNFRwOGpCZWdXbmx3V1k9BjsARg%3D%3D--c34c5fc6de928cde391cccd2b710547c7aab1d06
  * rack.request.query_hash                        : {}
  * rack.request.query_string                      : 
  * rack.run_once                                  : false
  * rack.session                                   : {"session_id"=>"f59bf3b7aa7c9ace32c939766e32d656", "_csrf_token"=>"no97EC4mADf02hVV3OfR+XqBl73Fk4Tp8jBegWnlwWY="}
  * rack.session.options                           : {:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :coder=>#<Rack::Session::Cookie::Base64::Marshal:0x007f88a3c54df0>, :id=>"f59bf3b7aa7c9ace32c939766e32d656"}
  * rack.url_scheme                                : http
  * rack.version                                   : [1, 1]
  
  * Process: 21906
  * Server : Foobar

-------------------------------
Backtrace:
-------------------------------

  app/controllers/home_controller.rb:3:in `show'
  actionpack (3.2.8) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
  actionpack (3.2.8) lib/abstract_controller/base.rb:167:in `process_action'
  actionpack (3.2.8) lib/action_controller/metal/rendering.rb:10:in `process_action'
  actionpack (3.2.8) lib/abstract_controller/callbacks.rb:18:in `block in process_action'
  activesupport (3.2.8) lib/active_support/callbacks.rb:414:in `_run__1225659172420690068__process_action__357961545621717090__callbacks'
  activesupport (3.2.8) lib/active_support/callbacks.rb:405:in `__run_callback'
  activesupport (3.2.8) lib/active_support/callbacks.rb:385:in `_run_process_action_callbacks'
  activesupport (3.2.8) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (3.2.8) lib/abstract_controller/callbacks.rb:17:in `process_action'
  actionpack (3.2.8) lib/action_controller/metal/rescue.rb:29:in `process_action'
  actionpack (3.2.8) lib/action_controller/metal/instrumentation.rb:30:in `block in process_action'
  activesupport (3.2.8) lib/active_support/notifications.rb:123:in `block in instrument'
  activesupport (3.2.8) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activesupport (3.2.8) lib/active_support/notifications.rb:123:in `instrument'
  actionpack (3.2.8) lib/action_controller/metal/instrumentation.rb:29:in `process_action'
  actionpack (3.2.8) lib/action_controller/metal/params_wrapper.rb:207:in `process_action'
  activerecord (3.2.8) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
  actionpack (3.2.8) lib/abstract_controller/base.rb:121:in `process'
  actionpack (3.2.8) lib/abstract_controller/rendering.rb:45:in `process'
  actionpack (3.2.8) lib/action_controller/metal.rb:203:in `dispatch'
  actionpack (3.2.8) lib/action_controller/metal/rack_delegation.rb:14:in `dispatch'
  actionpack (3.2.8) lib/action_controller/metal.rb:246:in `block in action'
  actionpack (3.2.8) lib/action_dispatch/routing/route_set.rb:73:in `call'
  actionpack (3.2.8) lib/action_dispatch/routing/route_set.rb:73:in `dispatch'
  actionpack (3.2.8) lib/action_dispatch/routing/route_set.rb:36:in `call'
  journey (1.0.4) lib/journey/router.rb:68:in `block in call'
  journey (1.0.4) lib/journey/router.rb:56:in `each'
  journey (1.0.4) lib/journey/router.rb:56:in `call'
  actionpack (3.2.8) lib/action_dispatch/routing/route_set.rb:600:in `call'
  exception_notification (2.5.2) lib/exception_notifier.rb:25:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'
  rack (1.4.1) lib/rack/etag.rb:23:in `call'
  rack (1.4.1) lib/rack/conditionalget.rb:25:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/head.rb:14:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/params_parser.rb:21:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/flash.rb:242:in `call'
  rack (1.4.1) lib/rack/session/abstract/id.rb:205:in `context'
  rack (1.4.1) lib/rack/session/abstract/id.rb:200:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/cookies.rb:339:in `call'
  activerecord (3.2.8) lib/active_record/query_cache.rb:64:in `call'
  activerecord (3.2.8) lib/active_record/connection_adapters/abstract/connection_pool.rb:473:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
  activesupport (3.2.8) lib/active_support/callbacks.rb:405:in `_run__3608295848955106148__call__1508032645417640755__callbacks'
  activesupport (3.2.8) lib/active_support/callbacks.rb:405:in `__run_callback'
  activesupport (3.2.8) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'
  activesupport (3.2.8) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (3.2.8) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/reloader.rb:65:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'
  railties (3.2.8) lib/rails/rack/logger.rb:26:in `call_app'
  railties (3.2.8) lib/rails/rack/logger.rb:16:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/request_id.rb:22:in `call'
  rack (1.4.1) lib/rack/methodoverride.rb:21:in `call'
  rack (1.4.1) lib/rack/runtime.rb:17:in `call'
  activesupport (3.2.8) lib/active_support/cache/strategy/local_cache.rb:72:in `call'
  rack (1.4.1) lib/rack/lock.rb:15:in `call'
  actionpack (3.2.8) lib/action_dispatch/middleware/static.rb:62:in `call'
  railties (3.2.8) lib/rails/engine.rb:479:in `call'
  railties (3.2.8) lib/rails/rack/log_tailer.rb:17:in `call'
  rack (1.4.1) lib/rack/handler/webrick.rb:59:in `service'
  /Users/foo/.rbenv/versions/1.9.2-p320/lib/ruby/1.9.1/webrick/httpserver.rb
:111:in `service'
  /Users/foo/.rbenv/versions/1.9.2-p320/lib/ruby/1.9.1/webrick/httpserver.rb:70:in `run'
  /Users/foo/.rbenv/versions/1.9.2-p320/lib/ruby/1.9.1/webrick/server.rb:183:in `block in start_thread'

既知の問題

exception_notification がメールを送るときにさらに例外が起こると、元の例外は raise されず、それがどんなものだったかわからなくなってしまいます (実は、この問題を修正するパッチを書いてたんですが、まだ pull request 送れてません...)。

運用時は、実際に例外を起こしてみて、ちゃんとメールが送られてくるか、必ず確認しておきましょう。

追記: Rails 4 対応

Rails 4 では config/initializers 下の設定ファイルの書き方が変わっています。

参考: https://github.com/smartinez87/exception_notification

# config/initializers/exception_notification.rb
Rails.application.config.middleware.use(
  ExceptionNotification::Rack,
  :email => {
    :email_prefix => "[アプリケーション名] ",
    :sender_address => %{"送信者名" <送信元メールアドレス>},
    :exception_recipients => %w{送信先メールアドレス}
  }
)
65
64
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
64

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?