例外オブジェクトの内容をメール通知する実装例

# はじめに

何かしら例外が発生したときに、そのエラー内容をメール通知する方法を書きます。
注意点として全ての例外を補足してメール通知すると大変なことになりそうなので、
特定の処理に絞るなど、仕様によって適時変更するといいと思います。

# 実装

```rb:hoge_controller.rb
# 何かしらのアクション
def fuga_action
  # 何かしらの処理
rescue => e
  logger.error e
  @error = "#{e.class} #{e}\n#{e.backtrace.join("\n")}" # 例外オブジェクトを加工する
  NotificationMailer.send_error_to_admin(@error).deliver # エラーを管理者に通知する
end
```

```rb:notification_mailer.rb
def send_error_to_admin(error)
  mail(
    from: DEFAULT_EMAIL, # 送信元
    to: ADMIN_EMAIL, # 送信先
    template_name: 'error', # 使用するテンプレート名
    subject: 'エラーが発生しました', # メールタイトル
    body: error, # メール本文
    content_type: 'text/plain' # メールタイプ
  )
end
```

`rescue`で補足した例外オブジェクトを加工してメーラーの引数として使います。
メーラー側で引数の内容をそのまま本文にしています。

例えば `ActiveRecord::RecordNotFound`が発生した時に、送信されるメール本文はこんな感じになります。

```
ActiveRecord::RecordNotFound ActiveRecord::RecordNotFound
/Users/username/repos/Project/app/services/project_service.rb:33:in `action_method'
/Users/username/repos/Project/app/services/project_service.rb:15:in `block (2 levels) in action_method'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/relation/delegation.rb:38:in `each'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/relation/delegation.rb:38:in `each'
/Users/username/repos/Project/app/services/mail_scenarios_service.rb:13:in `block in add_default_mail_magazine'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/connection_adapters/abstract/database_statements.rb:232:in `block in transaction'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/connection_adapters/abstract/transaction.rb:189:in `within_new_transaction'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/connection_adapters/abstract/database_statements.rb:232:in `transaction'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/transactions.rb:211:in `transaction'
/Users/username/repos/Project/app/services/mail_scenarios_service.rb:9:in `add_default_mail_magazine'
/Users/username/repos/Project/app/jobs/fetch_default_stepmail_job.rb:14:in `perform'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/execution.rb:34:in `block in perform_now'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:126:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:506:in `block (2 levels) in compile'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:455:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:448:in `block (2 levels) in around'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:286:in `block (2 levels) in halting'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/i18n-0.7.0/lib/i18n.rb:257:in `with_locale'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/translation.rb:7:in `block (2 levels) in <module:Translation>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:391:in `instance_exec'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:391:in `block in make_lambda'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:285:in `block in halting'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:447:in `block in around'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:455:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:448:in `block (2 levels) in around'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:286:in `block (2 levels) in halting'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/logging.rb:24:in `block (4 levels) in <module:Logging>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/notifications.rb:164:in `block in instrument'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/notifications/instrumenter.rb:21:in `instrument'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/notifications.rb:164:in `instrument'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/logging.rb:23:in `block (3 levels) in <module:Logging>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/logging.rb:44:in `block in tag_logger'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/tagged_logging.rb:70:in `block in tagged'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/tagged_logging.rb:26:in `tagged'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/tagged_logging.rb:70:in `tagged'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/logging.rb:44:in `tag_logger'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/logging.rb:20:in `block (2 levels) in <module:Logging>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:391:in `instance_exec'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:391:in `block in make_lambda'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:285:in `block in halting'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:447:in `block in around'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:455:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:101:in `__run_callbacks__'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:750:in `_run_perform_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:90:in `run_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/execution.rb:33:in `perform_now'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/execution.rb:22:in `block in execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:126:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:506:in `block (2 levels) in compile'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:455:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:448:in `block (2 levels) in around'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:286:in `block (2 levels) in halting'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/railtie.rb:26:in `block (4 levels) in <class:Railtie>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/execution_wrapper.rb:76:in `wrap'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/reloader.rb:68:in `block in wrap'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/execution_wrapper.rb:76:in `wrap'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/reloader.rb:67:in `wrap'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/railtie.rb:25:in `block (3 levels) in <class:Railtie>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:391:in `instance_exec'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:391:in `block in make_lambda'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:285:in `block in halting'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:447:in `block in around'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:455:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:101:in `__run_callbacks__'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:750:in `_run_execute_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.0.1/lib/active_support/callbacks.rb:90:in `run_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/execution.rb:20:in `execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/activejob-5.0.0.1/lib/active_job/queue_adapters/delayed_job_adapter.rb:36:in `perform'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/backend/base.rb:84:in `block in invoke_job'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:61:in `block in initialize'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:66:in `execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:40:in `run_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/backend/base.rb:81:in `invoke_job'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:230:in `block (2 levels) in run'
/usr/local/opt/rbenv/versions/2.3.1/lib/ruby/2.3.0/timeout.rb:91:in `block in timeout'
/usr/local/opt/rbenv/versions/2.3.1/lib/ruby/2.3.0/timeout.rb:101:in `timeout'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:230:in `block in run'
/usr/local/opt/rbenv/versions/2.3.1/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:229:in `run'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:306:in `block in reserve_and_run_one_job'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:61:in `block in initialize'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:66:in `execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:40:in `run_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:306:in `reserve_and_run_one_job'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:213:in `block in work_off'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:212:in `times'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:212:in `work_off'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:175:in `block (4 levels) in start'
/usr/local/opt/rbenv/versions/2.3.1/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:174:in `block (3 levels) in start'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:61:in `block in initialize'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:66:in `execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:40:in `run_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:173:in `block (2 levels) in start'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:172:in `loop'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:172:in `block in start'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/plugins/clear_locks.rb:7:in `block (2 levels) in <class:ClearLocks>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:79:in `block (2 levels) in add'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:61:in `block in initialize'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:79:in `block in add'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:66:in `execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/lifecycle.rb:40:in `run_callbacks'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/worker.rb:171:in `start'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/delayed_job-4.1.2/lib/delayed/tasks.rb:9:in `block (2 levels) in <top (required)>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/task.rb:240:in `block in execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/task.rb:235:in `each'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/task.rb:235:in `execute'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/task.rb:179:in `block in invoke_with_call_chain'
/usr/local/opt/rbenv/versions/2.3.1/lib/ruby/2.3.0/monitor.rb:214:in `mon_synchronize'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/task.rb:172:in `invoke_with_call_chain'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/task.rb:165:in `invoke'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/application.rb:150:in `invoke_task'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/application.rb:106:in `block (2 levels) in top_level'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/application.rb:106:in `each'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/application.rb:106:in `block in top_level'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/application.rb:115:in `run_with_threads'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/application.rb:100:in `top_level'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/railties-5.0.0.1/lib/rails/commands/rake_proxy.rb:13:in `block in run_rake_task'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/rake-10.4.2/lib/rake/application.rb:176:in `standard_exception_handling'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/railties-5.0.0.1/lib/rails/commands/rake_proxy.rb:10:in `run_rake_task'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:51:in `run_command!'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/railties-5.0.0.1/lib/rails/commands.rb:18:in `<top (required)>'
/Users/username/repos/Project/bin/rails:9:in `require'
/Users/username/repos/Project/bin/rails:9:in `<top (required)>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/client/rails.rb:28:in `load'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/client/rails.rb:28:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/client/command.rb:7:in `call'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/client.rb:30:in `run'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/bin/spring:49:in `<top (required)>'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/binstub.rb:11:in `load'
/Users/username/repos/Project/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/binstub.rb:11:in `<top (required)>'
/usr/local/opt/rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
/usr/local/opt/rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
/Users/username/repos/Project/bin/spring:13:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
```

### ポイント

```rb
@error = "#{e.class} #{e}\n#{e.backtrace.join("\n")}"
```

で例外オブジェクトを見やすく加工していることです。