Commit 3dde7609e7c14ead995a1670d15029f8b1bc36e9

Authored by daming
0 parents
Exists in master

first commit

.gitignore 0 → 100644
  1 +++ a/.gitignore
... ... @@ -0,0 +1,12 @@
  1 +/.bundle/
  2 +/.yardoc
  3 +/Gemfile.lock
  4 +/_yardoc/
  5 +/coverage/
  6 +/doc/
  7 +/pkg/
  8 +/spec/reports/
  9 +/tmp/
  10 +
  11 +# rspec failure tracking
  12 +.rspec_status
... ...
.rspec 0 → 100644
  1 +++ a/.rspec
... ... @@ -0,0 +1,2 @@
  1 +--format documentation
  2 +--color
... ...
.travis.yml 0 → 100644
  1 +++ a/.travis.yml
... ... @@ -0,0 +1,23 @@
  1 +language: ruby
  2 +cache: bundler
  3 +
  4 +before_install: gem install bundler
  5 +
  6 +rvm:
  7 + - 3.0.0
  8 + - 2.7.2
  9 + - 2.6.6
  10 + - 2.5.8
  11 + - 2.4.10
  12 +gemfile:
  13 + - gemfiles/Gemfile.rails_6.1
  14 + - gemfiles/Gemfile.rails_6.0
  15 + - gemfiles/Gemfile.rails_5.2
  16 +jobs:
  17 + exclude:
  18 + - rvm: 3.0.0
  19 + gemfile: gemfiles/Gemfile.rails_5.2.4
  20 + - rvm: 2.4.10
  21 + gemfile: gemfiles/Gemfile.rails_6.0
  22 + - rvm: 2.4.10
  23 + gemfile: gemfiles/Gemfile.rails_6.1
... ...
Gemfile 0 → 100644
  1 +++ a/Gemfile
... ... @@ -0,0 +1,4 @@
  1 +source 'https://rubygems.org'
  2 +
  3 +# Specify your gem's dependencies in exception_file_notifier.gemspec
  4 +gemspec
... ...
LICENSE.txt 0 → 100644
  1 +++ a/LICENSE.txt
... ... @@ -0,0 +1,21 @@
  1 +The MIT License (MIT)
  2 +
  3 +Copyright (c) 2017 Koji Onishi
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining a copy
  6 +of this software and associated documentation files (the "Software"), to deal
  7 +in the Software without restriction, including without limitation the rights
  8 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +copies of the Software, and to permit persons to whom the Software is
  10 +furnished to do so, subject to the following conditions:
  11 +
  12 +The above copyright notice and this permission notice shall be included in
  13 +all copies or substantial portions of the Software.
  14 +
  15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21 +THE SOFTWARE.
... ...
README.md 0 → 100644
  1 +++ a/README.md
... ... @@ -0,0 +1,93 @@
  1 +# Exception File Notifier
  2 +
  3 +[![Gem Version](https://badge.fury.io/rb/exception_file_notifier.svg)](https://badge.fury.io/rb/exception_file_notifier)
  4 +[![Travis](https://api.travis-ci.org/fursich/exception_file_notifier.png)](http://travis-ci.org/fursich/exception_file_notifier)
  5 +[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
  6 +
  7 +**Exception File Notifier** is a custom notifier for [Exception Notification](https://github.com/smartinez87/exception_notification), that records notifications onto a log file when errors occur in a Rack/Rails application.
  8 +
  9 +All the error logs are converted into JSON format. This would be useful typically when you wish to monitor an app with monitoring tools - e.g. Kibana + ElasticSearch + Fluentd, or anything alike.
  10 +
  11 +## Installation
  12 +
  13 +Add this line to your application's Gemfile:
  14 +
  15 +```ruby
  16 +gem 'exception_file_notifier'
  17 +```
  18 +
  19 +And then execute:
  20 +
  21 + $ bundle
  22 +
  23 +Or install it yourself as:
  24 +
  25 + $ gem install exception_file_notifier
  26 +
  27 +## Usage
  28 +
  29 +Set proper configs in *config/initializers/exception_notification.rb*
  30 +
  31 +To get started, use the simplest settings:
  32 +
  33 +```ruby:config/initializers/exception_notification.rb
  34 +ExceptionNotification.configure do |config|
  35 +
  36 + config.add_notifier :file, {
  37 + filename: "#{Rails.root}/log/exceptions.log"
  38 + }
  39 +```
  40 +
  41 +That works in all the environment (including test).
  42 +
  43 +You could also customize settings like so:
  44 +
  45 +```ruby:config/initializers/exception_notification.rb
  46 +ExceptionNotification.configure do |config|
  47 +
  48 +# disable all the notifiers in certain environment
  49 + config.ignore_if do |exception, options|
  50 + Rails.env.test?
  51 + end
  52 +
  53 + config.add_notifier :file, {
  54 + filename: "#{Rails.root}/log/exceptions_#{Rails.env}.log", # generate different log files depending on environments
  55 + shift_age: 'daily' # use shift_age/shift_size options to rotate log files
  56 + }
  57 +```
  58 +
  59 +You could also pass as many original values as you like, which will be evaluated JSON-ified at the time when an exception occurs. For detailed setting you may wish to consult with the Exception Notification [original readme](https://github.com/smartinez87/exception_notification).
  60 +
  61 +#### available options:
  62 +
  63 +- filename: specify the log file (preferablly with its absolute path)
  64 +
  65 +- shift_age: option for log file rotation: directly passed to Ruby Logger
  66 +
  67 +- shift_size: option for log file rotation: directly passed to Ruby Logger
  68 +
  69 +(for the latter options see also: https://docs.ruby-lang.org/ja/latest/method/Logger/s/new.html)
  70 +
  71 +#### Note
  72 +
  73 +Due to [a bug](https://bugs.ruby-lang.org/issues/12948) with ruby Logger discovered in ruby 2.2 - 2.3, it might happen that you cannot rotate logs by using shift_age. (for those who come up with any workaround for this, please let us know / PR are welcomed)
  74 +
  75 +Meanwhile you could cope with either 1) upgrading your ruby version upto 2.4, or 2) rotate logs by size, not date
  76 +
  77 +## Development
  78 +
  79 +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
  80 +
  81 +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
  82 +
  83 +## Contributing
  84 +
  85 +Bug reports and pull requests are welcome on GitHub at https://github.com/fursich/exception_file_notifier.
  86 +
  87 +## Special Thanks To
  88 +
  89 +All the folks who have given supports, especially @motchang and @katsurak for great advises and reviews.
  90 +
  91 +## License
  92 +
  93 +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
... ...
Rakefile 0 → 100644
  1 +++ a/Rakefile
... ... @@ -0,0 +1,6 @@
  1 +require "bundler/gem_tasks"
  2 +require "rspec/core/rake_task"
  3 +
  4 +RSpec::Core::RakeTask.new(:spec)
  5 +
  6 +task :default => :spec
... ...
bin/console 0 → 100755
  1 +++ a/bin/console
... ... @@ -0,0 +1,14 @@
  1 +#!/usr/bin/env ruby
  2 +
  3 +require "bundler/setup"
  4 +require "exception_file_notifier"
  5 +
  6 +# You can add fixtures and/or initialization code here to make experimenting
  7 +# with your gem easier. You can also use a different console, if you like.
  8 +
  9 +# (If you use this, don't forget to add pry to your Gemfile!)
  10 +# require "pry"
  11 +# Pry.start
  12 +
  13 +require "irb"
  14 +IRB.start(__FILE__)
... ...
bin/setup 0 → 100755
  1 +++ a/bin/setup
... ... @@ -0,0 +1,8 @@
  1 +#!/usr/bin/env bash
  2 +set -euo pipefail
  3 +IFS=$'\n\t'
  4 +set -vx
  5 +
  6 +bundle install
  7 +
  8 +# Do any other automated setup that you need to do here
... ...
exception_file_notifier.gemspec 0 → 100644
  1 +++ a/exception_file_notifier.gemspec
... ... @@ -0,0 +1,42 @@
  1 +# coding: utf-8
  2 +lib = File.expand_path('../lib', __FILE__)
  3 +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
  4 +require 'exception_notifier/exception_file_notifier/version'
  5 +
  6 +Gem::Specification.new do |spec|
  7 + spec.name = "exception_file_notifier"
  8 + spec.version = ::ExceptionNotifier::ExceptionFileNotifier::VERSION
  9 + spec.authors = ["Koji Onishi"]
  10 + spec.email = ["fursich0@gmail.com"]
  11 +
  12 + spec.summary = %q{ A custom notifier for ExceptionNotification that generates exception log files in JSON format. }
  13 + spec.description = %q{ Exception File Notifier records exception logs in JSON format, helping you track errors in the production environment. }
  14 + spec.homepage = "https://github.com/fursich/exception_file_notifier"
  15 + spec.license = "MIT"
  16 +
  17 + # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
  18 + # to allow pushing to a single host or delete this section to allow pushing to any host.
  19 + # if spec.respond_to?(:metadata)
  20 + # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'
  21 + # else
  22 + # raise "RubyGems 2.0 or newer is required to protect against " \
  23 + # "public gem pushes."
  24 + # end
  25 +
  26 + spec.files = `git ls-files -z`.split("\x0").reject do |f|
  27 + f.match(%r{^(test|spec|features)/})
  28 + end
  29 + spec.bindir = "exe"
  30 + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  31 + spec.require_paths = ["lib"]
  32 +
  33 + spec.required_ruby_version = '>= 2.3'
  34 + spec.required_rubygems_version = '>= 1.8.11'
  35 + spec.add_dependency "exception_notification", ">= 4.0"
  36 + spec.add_dependency("activesupport", ">= 4.0", "<7")
  37 +
  38 + spec.add_development_dependency "bundler", ">= 2"
  39 + spec.add_development_dependency "rake", ">= 10.0"
  40 + spec.add_development_dependency "rspec", ">= 3.10"
  41 + spec.add_development_dependency "pry"
  42 +end
... ...
gemfiles/Gemfile.rails_5.2 0 → 100644
  1 +++ a/gemfiles/Gemfile.rails_5.2
... ... @@ -0,0 +1,6 @@
  1 +source "https://rubygems.org"
  2 +
  3 +# Specify your gem's dependencies in dumped_railers.gemspec
  4 +gem 'activesupport', '5.2.4.4'
  5 +
  6 +gemspec path: '../'
... ...
gemfiles/Gemfile.rails_6.0 0 → 100644
  1 +++ a/gemfiles/Gemfile.rails_6.0
... ... @@ -0,0 +1,6 @@
  1 +source "https://rubygems.org"
  2 +
  3 +# Specify your gem's dependencies in dumped_railers.gemspec
  4 +gem 'activesupport', '6.0.3.4'
  5 +
  6 +gemspec path: '../'
... ...
gemfiles/Gemfile.rails_6.1 0 → 100644
  1 +++ a/gemfiles/Gemfile.rails_6.1
... ... @@ -0,0 +1,6 @@
  1 +source "https://rubygems.org"
  2 +
  3 +# Specify your gem's dependencies in dumped_railers.gemspec
  4 +gem 'activesupport', '6.1.0'
  5 +
  6 +gemspec path: '../'
... ...
lib/exception_file_notifier.rb 0 → 100644
  1 +++ a/lib/exception_file_notifier.rb
... ... @@ -0,0 +1,2 @@
  1 +require "exception_notifier/exception_file_notifier/version"
  2 +require "exception_notifier/file_notifier"
... ...
lib/exception_notifier/exception_file_notifier/version.rb 0 → 100644
  1 +++ a/lib/exception_notifier/exception_file_notifier/version.rb
... ... @@ -0,0 +1,5 @@
  1 +module ExceptionNotifier
  2 + module ExceptionFileNotifier
  3 + VERSION = "0.1.0".freeze
  4 + end
  5 +end
... ...
lib/exception_notifier/file_notifier.rb 0 → 100644
  1 +++ a/lib/exception_notifier/file_notifier.rb
... ... @@ -0,0 +1,155 @@
  1 +require "exception_notification"
  2 +require "action_dispatch"
  3 +require "active_support/core_ext/hash"
  4 +require "socket"
  5 +require 'logger'
  6 +require 'json'
  7 +
  8 +module ExceptionNotifier
  9 + class FileNotifier < BaseNotifier
  10 + include ExceptionNotifier::BacktraceCleaner
  11 +
  12 + def initialize(options={})
  13 + filename = options[:filename] || "log/error.log"
  14 + shift_age = options[:shift_age] || 0
  15 + shift_size = options[:shift_size] || nil
  16 +
  17 + @logger_options = [filename, shift_age, shift_size]
  18 + @formatter = -> (_, _, _, message) { message + "\n" }
  19 + end
  20 +
  21 + def call(exception=nil, options={})
  22 + append_log exception_message(options[:env], exception, options)
  23 + end
  24 +
  25 + def append_log(message)
  26 + log = ::Logger.new(*@logger_options)
  27 + log.formatter = @formatter
  28 + error_log = generate_json(message)
  29 + log.error(error_log)
  30 + log.close
  31 + end
  32 +
  33 + def generate_json(message)
  34 + ::JSON.generate(deep_encode(message))
  35 + end
  36 +
  37 + def exception_message(env, exception, options={})
  38 + return error_message(exception) unless exception.is_a?(Exception)
  39 +
  40 + if env.present?
  41 + exception_notification(options[:env], exception, options)
  42 + else
  43 + background_exception_notification(exception, options)
  44 + end
  45 + end
  46 +
  47 + def exception_notification(env, exception, options)
  48 + @env = env
  49 + @exception = exception
  50 + @options = options.reverse_merge(@env['exception_notifier.options'] || {}).symbolize_keys
  51 + @kontroller = @env['action_controller.instance'] || MissingController.new
  52 + @request = ::ActionDispatch::Request.new(@env)
  53 + @backtrace = exception.backtrace ? clean_backtrace(exception) : []
  54 + @timestamp = ::Time.current
  55 + @data = (@env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
  56 +
  57 + render_notice
  58 + end
  59 +
  60 + def background_exception_notification(exception, options)
  61 + @exception = exception
  62 + @options = options.symbolize_keys
  63 + @backtrace = exception.backtrace || []
  64 + @timestamp = ::Time.current
  65 + @data = options[:data] || {}
  66 + @env = @kontroller = nil
  67 +
  68 + render_notice
  69 + end
  70 +
  71 + def error_message(exception)
  72 + {
  73 + message: '[FATAL] NO EXCEPTION GIVEN: Please provide Exception as argument',
  74 + exception: exception.try(:to_s),
  75 + timestamp: ::Time.current,
  76 + }
  77 + end
  78 +
  79 + def render_notice
  80 + set_data_variables
  81 +
  82 + exception_details = {
  83 + exception: @exception.class,
  84 + controller: @kontroller.present? ? @kontroller.controller_name : '[BACKGROUND]',
  85 + action: @kontroller.present? ? @kontroller.action_name : '[BACKGROUND]',
  86 + exception_message: @exception.message,
  87 +
  88 + timestamp: @timestamp,
  89 + server: ::Socket.gethostname,
  90 + rails_root: (defined?(::Rails) && ::Rails.respond_to?(:root) ) ? ::Rails.root : nil,
  91 + process: $$,
  92 + backtrace: @backtrace,
  93 + }
  94 +
  95 + if @env.present?
  96 + exception_details.merge!({
  97 + url: @request.url,
  98 + http_method: @request.request_method,
  99 + ip_address: @request.remote_ip,
  100 + parameters: (@request.filtered_parameters.inspect rescue "[NOT ENCODABLE]"),
  101 + session_id: (@request.ssl? ? "[FILTERED]" : @request.session['session_id'] || (@request.env["rack.session.options"] and @request.env["rack.session.options"][:id])),
  102 + session_data: @request.session.to_hash,
  103 + })
  104 + filtered_env = @request.filtered_env
  105 + filtered_env.sort_by { |key, _| key.to_s }.each do |key, value|
  106 + exception_details[key] = value
  107 + end
  108 + end
  109 +
  110 + exception_details.merge!({
  111 + data: @data,
  112 + })
  113 +
  114 + exception_details
  115 + end
  116 +
  117 + class MissingController
  118 + def controller_name
  119 + '[NO CONTROLLER]'
  120 + end
  121 + def action_name
  122 + '[NO CONTROLLER]'
  123 + end
  124 + def method_missing(*args, &block)
  125 + end
  126 + end
  127 +
  128 + def set_data_variables
  129 + @data.each do |name, value|
  130 + instance_variable_set("@#{name}", value)
  131 + end
  132 + end
  133 +
  134 + def deep_encode(obj)
  135 + case obj
  136 + when Array
  137 + obj.map { |o| deep_encode(o) }
  138 + when Hash
  139 + deep_encode(obj.to_a).to_h
  140 + when String
  141 + encode_to_utf8(obj)
  142 + else
  143 + obj.respond_to?(:to_s) ? encode_to_utf8(obj.to_s) : '[NOT ENCODABLE]'
  144 + end
  145 + end
  146 +
  147 + def encode_to_utf8(str)
  148 + quick_sanitization(str).encode(::Encoding.find('UTF-8'), invalid: :replace, undef: :replace, replace: '?')
  149 + end
  150 +
  151 + def quick_sanitization(str) # stringify any random objects in a safe (and convenient) manner
  152 + str.inspect.gsub(/\A\"(.*)\"\z/,'\1')
  153 + end
  154 + end
  155 +end
... ...
spec/exception_notifier/exception_file_notifier_spec.rb 0 → 100644
  1 +++ a/spec/exception_notifier/exception_file_notifier_spec.rb
... ... @@ -0,0 +1,7 @@
  1 +require "spec_helper"
  2 +
  3 +RSpec.describe ExceptionNotifier::ExceptionFileNotifier do
  4 + it "has a version number" do
  5 + expect(ExceptionNotifier::ExceptionFileNotifier::VERSION).not_to be nil
  6 + end
  7 +end
... ...
spec/exception_notifier/file_notifier_spec.rb 0 → 100644
  1 +++ a/spec/exception_notifier/file_notifier_spec.rb
... ... @@ -0,0 +1,161 @@
  1 +require "spec_helper"
  2 +require "exception_test_helper"
  3 +
  4 +RSpec.describe ExceptionNotifier::FileNotifier do
  5 + include ExceptionTestHelper
  6 +
  7 + let(:notifier) {
  8 + ExceptionNotifier::FileNotifier.new(
  9 + options
  10 + )
  11 + }
  12 +
  13 + let(:options) {
  14 + {
  15 + filename: filename,
  16 + shift_age: shift_age,
  17 + shift_size: shift_size,
  18 + }.compact
  19 + }
  20 +
  21 + let(:filename) { 'log/exceptions.log' }
  22 + let(:shift_age) { 'daily' }
  23 + let(:shift_size) { nil }
  24 +
  25 + describe '#initialize' do
  26 + let(:logger_options) {
  27 + notifier.instance_eval {@logger_options}
  28 + }
  29 + it 'constructs the right file name based on given options' do
  30 + expect(logger_options).to contain_exactly(filename, shift_age, shift_size)
  31 + end
  32 + end
  33 +
  34 + describe '#generate_json' do
  35 + context 'in case broken strings are given' do
  36 + let (:hash_with_broken_codes) {
  37 + {
  38 + French: 'La langue française'.force_encoding('ASCII'),
  39 + Russian: 'Русский язык'.encode('WINDOWS-31J').force_encoding('UTF-16'),
  40 + Japanese: {'日本語'.encode('UTF-8').force_encoding('WINDOWS-31J') => 'にほんご'.force_encoding('ASCII') },
  41 + }
  42 + }
  43 +
  44 + it 'jasonifies the strings properly without causing errors' do
  45 + expect( JSON.parse(notifier.generate_json(hash_with_broken_codes)) ).to eq(
  46 + {
  47 + "French"=>"La langue fran\\xC3\\xA7aise",
  48 + "Russian"=>"\\x84\\x51\\x84\\x85\\x84\\x83\\x84\\x83\\x84\\x7B\\x84\\x79\\x84\\x7A\\x20\\x84\\x91\\x84\\x78\\x84\\x8D\\x84\\x7B",
  49 + "Japanese" => {"\\x{E697}\\xA5\\x{E69C}\\xAC\\x{E8AA}\\x9E"=>"\\xE3\\x81\\xAB\\xE3\\x81\\xBB\\xE3\\x82\\x93\\xE3\\x81\\x94"},
  50 + }
  51 + )
  52 + end
  53 + end
  54 + end
  55 +
  56 + describe '#call' do
  57 + let (:backtrace) {
  58 + [
  59 + %(/test/app/controllers/api/dummy_controller.rb:123:in `build_whatever_params'),
  60 + %(/test/app/controllers/api/dummy_controller.rb:321:in `any_random_loop'),
  61 + ]
  62 + }
  63 +
  64 + context 'with env options' do
  65 + let (:env) {
  66 + { "rack.version" => [1, 2],
  67 + "action_controller.instance" => ExceptionTestHelper::DummyController.new,
  68 + "REQUEST_METHOD" => "GET",
  69 + "SERVER_NAME" => "example.org",
  70 + "SERVER_PORT" => "80",
  71 + "PATH_INFO" => "/category",
  72 + "rack.url_scheme" => "http",
  73 + "REMOTE_ADDR" => "127.0.0.1",
  74 + "HTTP_HOST" => "example.org",
  75 + }
  76 + }
  77 +
  78 + it 'indicates the exception' do
  79 + expect(notifier).to receive(:append_log).with(
  80 + hash_including(
  81 + exception: ExceptionTestHelper::DummyException,
  82 + exception_message: 'Dummy Exception Message'
  83 + )
  84 + ).once
  85 + notifier.call(ExceptionTestHelper::DummyException.new(backtrace), env: env)
  86 + end
  87 +
  88 + it 'provides controller and action name' do
  89 + expect(notifier).to receive(:append_log).with(
  90 + hash_including(
  91 + controller: 'DummyController',
  92 + action: 'TestAction'
  93 + )
  94 + ).once
  95 + notifier.call(ExceptionTestHelper::DummyException.new(backtrace), env: env)
  96 + end
  97 +
  98 + it 'provides url' do
  99 + expect(notifier).to receive(:append_log).with(
  100 + hash_including(
  101 + url: "http://example.org/category"
  102 + )
  103 + ).once
  104 + notifier.call(ExceptionTestHelper::DummyException.new(backtrace), env: env)
  105 + end
  106 +
  107 + it 'provides backtrace' do
  108 + expect(notifier).to receive(:append_log).with(
  109 + hash_including(backtrace: backtrace)
  110 + ).once
  111 + notifier.call(ExceptionTestHelper::DummyException.new(backtrace), env: env)
  112 + end
  113 + end
  114 +
  115 + context 'without controller' do
  116 + let (:env) {
  117 + { "rack.version" => [1, 2],
  118 + "REQUEST_METHOD" => "GET",
  119 + "SERVER_NAME" => "example.org",
  120 + "SERVER_PORT" => "80",
  121 + "PATH_INFO" => "/category",
  122 + "rack.url_scheme" => "http",
  123 + "REMOTE_ADDR" => "127.0.0.1",
  124 + "HTTP_HOST" => "example.org",
  125 + }
  126 + }
  127 + it 'does not provide controller and action name' do
  128 + expect(notifier).to receive(:append_log).with(
  129 + hash_including(
  130 + controller: '[NO CONTROLLER]',
  131 + action: '[NO CONTROLLER]'
  132 + )
  133 + ).once
  134 + notifier.call(ExceptionTestHelper::DummyException.new(backtrace), env: env)
  135 + end
  136 + end
  137 +
  138 + context 'without env options' do
  139 + it 'indicates the exception' do
  140 + expect(notifier).to receive(:append_log).with(
  141 + hash_including(
  142 + exception: ExceptionTestHelper::DummyException,
  143 + exception_message: 'Dummy Exception Message'
  144 + )
  145 + )
  146 + notifier.call(ExceptionTestHelper::DummyException.new)
  147 + end
  148 +
  149 + it 'does not provide controller and action name' do
  150 + expect(notifier).to receive(:append_log).with(
  151 + hash_including(
  152 + controller: '[BACKGROUND]',
  153 + action: '[BACKGROUND]'
  154 + )
  155 + )
  156 + notifier.call(ExceptionTestHelper::DummyException.new)
  157 + end
  158 + end
  159 + end
  160 +
  161 +end
... ...
spec/exception_test_helper.rb 0 → 100644
  1 +++ a/spec/exception_test_helper.rb
... ... @@ -0,0 +1,23 @@
  1 +module ExceptionTestHelper
  2 +
  3 + class DummyException < Exception
  4 + def initialize(backtrace = nil)
  5 + super
  6 + set_backtrace(backtrace)
  7 + end
  8 +
  9 + def message
  10 + 'Dummy Exception Message'
  11 + end
  12 + end
  13 +
  14 + class DummyController
  15 + def controller_name
  16 + 'DummyController'
  17 + end
  18 +
  19 + def action_name
  20 + 'TestAction'
  21 + end
  22 + end
  23 +end
... ...
spec/spec_helper.rb 0 → 100644
  1 +++ a/spec/spec_helper.rb
... ... @@ -0,0 +1,20 @@
  1 +require "bundler/setup"
  2 +require "exception_file_notifier"
  3 +require 'exception_notification'
  4 +
  5 +if RUBY_VERSION >= '2.7.2'
  6 + # NOTE: https://bugs.ruby-lang.org/issues/17000
  7 + # this will keep us informed of deprecation warnings after Ruby 2.7.2
  8 + Warning[:deprecated] = true
  9 +end
  10 +
  11 +RSpec.configure do |config|
  12 + # Enable flags like --only-failures and --next-failure
  13 + config.example_status_persistence_file_path = ".rspec_status"
  14 +
  15 + config.expect_with :rspec do |c|
  16 + c.syntax = :expect
  17 + end
  18 +end
  19 +
  20 +# ENV["RAILS_ENV"] = "test"
... ...