coaster 1.3.9 → 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6eaa32341f872f74353087082f1f7d750ed734a1adbcf1d26b841ca2e7c7acfd
4
- data.tar.gz: 1cdab7090331ac145b31f1f780c3e15b5308fdd3361a694853a0af0274f2c2d9
3
+ metadata.gz: 5b2e4ea7b170f104949852f0413f81dac320102b8045377554d8fcc8034004fd
4
+ data.tar.gz: d20610716bddf3fada3e41f6b9762738ce0eaa3aca7072e5b612d93539d10d0c
5
5
  SHA512:
6
- metadata.gz: 6300e186843cce7e421df49de7af1c9329814f8e0655bc436fc5b3e54006dc4da388d8b7a2458958b99ec1e9a95da36d126c861abe86bf3c6c27a93049718a78
7
- data.tar.gz: 862b2465610b61ee0497820f72912091dc461be1e742758621c24bc6624d22122fb14dc797462f19e3f1c8b7a0b3abb9231c7fc5ea4a1832d8519fc564d1f466
6
+ metadata.gz: bf519218cfcabfbe77f9cc8f6f56aafa6810048e443098fa725b0039684806413da62130545265969d43bea81f185811362c35dc1330bac688497fbd5af99287
7
+ data.tar.gz: 283eb1f6a28247e902bdc986f244b249ceace63fb661c89700e0fe914b598da2acca9147334988de6d472c9327549e256ad4c34f611974dd5977e94053aedd3e
data/README.md CHANGED
@@ -79,10 +79,139 @@ Hash로 전달되는 특수한 attribute가 있다.
79
79
 
80
80
 
81
81
  그 외에 error instance variable로 등록되는 attribute가 있다.
82
- 1. tags: [ActiveSupport::TaggedLogging|http://api.rubyonrails.org/classes/ActiveSupport/TaggedLogging.html]에 사용된다.
82
+ 1. tags: [ActiveSupport::TaggedLogging](http://api.rubyonrails.org/classes/ActiveSupport/TaggedLogging.html)에 사용된다.
83
83
  1. level: debug, info 등등의 로깅 레벨
84
84
  1. tkey: Object Translation 에서 사용된다. 기본값은 '.self'와 동일하다.
85
85
 
86
- ## StandardError raven extenstion
86
+ ## StandardError#logging
87
+
88
+ `StandardError#logging`으로 로깅한다.
89
+ logger는 `Coaster.logger=`를 사용하며 설정이 안돼있을 경우 `Rails.logger`를 사용한다.
90
+ cleaner(`AcitveSupport::BacktraceCleaner`)는 `StandardError.cleaner=`, `StandardError.cause_cleaner=`를 사용하며
91
+ 기본값은 없으며 cleaner가 없을 경우 backtrace를 출력하지 않는다.
92
+
93
+ 1. options
94
+ 1. `:logger` => 기본 logger를 대체할 logger
95
+ 1. `:cleaner` => 해당 에러를 로깅할때 사용할 cleaner
96
+ 1. before_logging_blocks, after_loggin_blocks
97
+ 1. `logging` 전후 처리를 추가할 수 있으며 추가된 block은 error instance 내에서 실행된다.
98
+ 1. ```
99
+ StandardError.before_logging(:cloudwatch) do
100
+ ReportCloudWatch.send(self) # self가 에러 자신
101
+ end
102
+ ```
103
+ 1. log 내용은 `StandardError#to_detail`을 사용
104
+ 1. Sentry
105
+ 아래와 같이 require를 하면 logging 하기 전 sentry에 보낸다.
106
+ ```
107
+ require 'coaster/core_ext/standard_error/sentry'
108
+ ```
109
+ ([raven.rb](lib/coaster/core_ext/standard_error/raven.rb)는 legacy, 옛날 sentry gem 이름)
110
+
111
+ ## StandardError#to_detail
112
+ `logging` 메서드에서 출력한 메시지를 만든다.
113
+
114
+ 1. error class, status, message, instance_variables(, backtrace) 순서대로 출력하며
115
+ cause가 존재할 경우 CAUSE이후 tab indent를 하여 출력한다. cause는 최대 3 depth까지 출력한다.
116
+ 1. instance_variable
117
+ 1. `StandardError.detail_vars` Array에서 있는 값의 출력은 `StandardError.detail_value_proc`으로 출력한다.
118
+ 1. `detail_vars` 기본값은 `%i[@attributes @tkey @fingerprint @tags @level]`
119
+ 1. `detail_value_proc` 기본값은 `Proc.new{|val| val.inspect}`
120
+ 1. 나머지는 `StandardError.detail_value_simpe`로 처리하며 class name만 사용한다.
121
+
122
+ ## coaster/rails_ext/backtrace_cleaner
123
+
124
+ [`AcitveSupport::BacktraceCleaner`](https://github.com/rails/rails/blob/main/activesupport/lib/active_support/backtrace_cleaner.rb)에서
125
+ 앞쪽은 `silence!`에서 제외하는(즉 모든 backtrace가 포함되는) 로직이 추가된다.
126
+ 앞쪽에서 얼마나 포함할지는 `cleaner.minimum_first=`로 설정하며 기본값은 10이다.
127
+ minimum_first 이후 silence된 backtrace사이에 `BacktraceCleaner.minimum_first ... and next silenced backtraces`라인이 끼워진다.
128
+
129
+ ## StandardError logging example with backtrace cleaner
87
130
 
88
- ...
131
+ ```
132
+ [Dynamoid::Errors::RecordNotUnique] status:999999
133
+ MESSAGE: Attempted to write record #<DynamoUserIdentificationLog:0x00005592491a7378> when its key already exists
134
+ @attributes: {}
135
+ @fingerprint: [:default, :class]
136
+ @inner_exception: Attempted to write record #<DynamoUserIdentificationLog:0x00005592491a7378> when its key already exists
137
+ @level: "error"
138
+ @original_exception: Dynamoid::Errors::ConditionalCheckFailedException
139
+ @raven: {}
140
+ @tags: {}
141
+ @tkey: nil
142
+ BACKTRACE:
143
+ dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:30:in `rescue in call'
144
+ dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:15:in `call'
145
+ dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:8:in `call'
146
+ dynamoid (3.7.1) lib/dynamoid/persistence.rb:485:in `block (2 levels) in save'
147
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:106:in `run_callbacks'
148
+ dynamoid (3.7.1) lib/dynamoid/persistence.rb:484:in `block in save'
149
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:106:in `run_callbacks'
150
+ dynamoid (3.7.1) lib/dynamoid/persistence.rb:483:in `save'
151
+ dynamoid (3.7.1) lib/dynamoid/dirty.rb:50:in `save'
152
+ dynamoid (3.7.1) lib/dynamoid/validations.rb:19:in `save'
153
+ BacktraceCleaner.minimum_first ... and next silenced backtraces
154
+ exmaple_app/app/models/dynamo_user_identification_log.rb:39:in `record!'
155
+ exmaple_app/app/models/sample_model.rb:156:in `record_authentication_log'
156
+ vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
157
+ vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
158
+ /home/circleci/.rubygems/bin/bundle:25:in `load'
159
+ /home/circleci/.rubygems/bin/bundle:25:in `<main>'
160
+ CAUSE: [Dynamoid::Errors::ConditionalCheckFailedException] status:999999
161
+ MESSAGE: The conditional request failed
162
+ @attributes: {}
163
+ @fingerprint: [:default, :class]
164
+ @inner_exception: Aws::DynamoDB::Errors::ConditionalCheckFailedException
165
+ @level: "error"
166
+ @raven: {}
167
+ @tags: {}
168
+ @tkey: nil
169
+ BACKTRACE:
170
+ dynamoid (3.7.1) lib/dynamoid/adapter_plugin/aws_sdk_v3.rb:471:in `rescue in put_item'
171
+ dynamoid (3.7.1) lib/dynamoid/adapter_plugin/aws_sdk_v3.rb:462:in `put_item'
172
+ dynamoid (3.7.1) lib/dynamoid/adapter.rb:153:in `block (3 levels) in <class:Adapter>'
173
+ dynamoid (3.7.1) lib/dynamoid/adapter.rb:56:in `benchmark'
174
+ dynamoid (3.7.1) lib/dynamoid/adapter.rb:153:in `block (2 levels) in <class:Adapter>'
175
+ dynamoid (3.7.1) lib/dynamoid/adapter.rb:71:in `write'
176
+ dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:24:in `call'
177
+ dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:8:in `call'
178
+ dynamoid (3.7.1) lib/dynamoid/persistence.rb:485:in `block (2 levels) in save'
179
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:106:in `run_callbacks'
180
+ BacktraceCleaner.minimum_first ... and next silenced backtraces
181
+ exmaple_app/app/models/dynamo_user_identification_log.rb:39:in `record!'
182
+ exmaple_app/app/models/sample_model.rb:156:in `record_authentication_log'
183
+ vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
184
+ vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
185
+ /home/circleci/.rubygems/bin/bundle:25:in `load'
186
+ /home/circleci/.rubygems/bin/bundle:25:in `<main>'
187
+ CAUSE: [Aws::DynamoDB::Errors::ConditionalCheckFailedException] status:999999
188
+ MESSAGE: The conditional request failed
189
+ @attributes: {}
190
+ @code: ConditionalCheckFailedException
191
+ @context: Seahorse::Client::RequestContext
192
+ @data: Aws::DynamoDB::Types::ConditionalCheckFailedException
193
+ @fingerprint: [:default, :class]
194
+ @level: "error"
195
+ @message: The conditional request failed
196
+ @raven: {}
197
+ @tags: {}
198
+ @tkey: nil
199
+ BACKTRACE:
200
+ aws-sdk-core (3.130.0) lib/seahorse/client/plugins/raise_response_errors.rb:17:in `call'
201
+ aws-sdk-dynamodb (1.69.0) lib/aws-sdk-dynamodb/plugins/simple_attributes.rb:119:in `call'
202
+ aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/jsonvalue_converter.rb:22:in `call'
203
+ aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'
204
+ aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'
205
+ aws-sdk-core (3.130.0) lib/seahorse/client/plugins/request_callback.rb:71:in `call'
206
+ aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'
207
+ aws-sdk-core (3.130.0) lib/seahorse/client/plugins/response_target.rb:24:in `call'
208
+ aws-sdk-core (3.130.0) lib/seahorse/client/request.rb:72:in `send_request'
209
+ aws-sdk-dynamodb (1.69.0) lib/aws-sdk-dynamodb/client.rb:4147:in `put_item'
210
+ BacktraceCleaner.minimum_first ... and next silenced backtraces
211
+ example_app/app/models/dynamo_user_identification_log.rb:39:in `record!'
212
+ example_app/app/models/sample_model.rb:156:in `record_authentication_log'
213
+ vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
214
+ vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
215
+ /home/circleci/.rubygems/bin/bundle:25:in `load'
216
+ /home/circleci/.rubygems/bin/bundle:25:in `<main>'
217
+ ```
@@ -1,7 +1,7 @@
1
1
  require 'attr_extras' # gem
2
2
 
3
3
  class Month
4
- vattr_initialize :year, :month
4
+ vattr_initialize :_year, :_month
5
5
 
6
6
  class << self
7
7
  def from(object)
@@ -19,7 +19,7 @@ class Month
19
19
  date = Date.parse(str)
20
20
  from(date)
21
21
  rescue ArgumentError => e
22
- if str.instance_variable_get(:@_gsub_)
22
+ if str.instance_variable_defined?(:@_gsub_) && str.instance_variable_get(:@_gsub_)
23
23
  raise e, str: str.instance_variable_get(:@_gsub_)
24
24
  elsif e.message != 'invalid date'
25
25
  raise e, str: str
@@ -41,11 +41,11 @@ class Month
41
41
  end
42
42
 
43
43
  def year
44
- Integer(@year)
44
+ Integer(@_year)
45
45
  end
46
46
 
47
47
  def month
48
- Integer(@month)
48
+ Integer(@_month)
49
49
  end
50
50
 
51
51
  def first_date
@@ -14,14 +14,14 @@ class Object
14
14
  if key.start_with?('.')
15
15
  subkey = key
16
16
  else
17
- return I18n.t(key, *args, options)
17
+ return I18n.t(key, *args, **options)
18
18
  end
19
19
  elsif key.is_a?(Symbol)
20
20
  subkey = ".#{key.to_s}"
21
21
  elsif key.nil?
22
22
  # do nothing
23
23
  else
24
- return I18n.t(key, *args, options)
24
+ return I18n.t(key, *args, **options)
25
25
  end
26
26
 
27
27
  key_class = options.delete(:class) || self
@@ -35,7 +35,7 @@ class Object
35
35
  options[:tkey] ||= key
36
36
  options.merge!(throw: true)
37
37
  result = catch(:exception) do
38
- I18n.t(key, *args, options)
38
+ I18n.t(key, *args, **options)
39
39
  end
40
40
 
41
41
  if result.is_a?(I18n::MissingTranslation)
@@ -31,6 +31,8 @@ class StandardError
31
31
 
32
32
  nt[:tags] ||= (tags && tags.merge(nt[:tags] || {})) || {}
33
33
  nt[:tags] = nt[:tags].merge(environment: Rails.env) if defined?(Rails)
34
+ nt[:tags][:digest_message] = digest_message if digest_message.present?
35
+ nt[:tags][:digest_backtrace] = digest_backtrace if digest_backtrace.present?
34
36
  nt[:level] ||= self.level
35
37
  nt[:extra] = attributes.merge(nt[:extra])
36
38
  nt
@@ -1,17 +1,33 @@
1
+ require 'digest'
1
2
  require 'coaster/core_ext/object_translation'
3
+ require 'coaster/rails_ext/backtrace_cleaner'
2
4
  require 'pp'
3
5
 
4
6
  class StandardError
5
7
  cattr_accessor :cleaner, :cause_cleaner
6
- cattr_accessor :to_log_detail_value, default: Proc.new{|val| val.inspect}
8
+
9
+ DEFAULT_INSPECTION_VARS = %i[@attributes @tkey @fingerprint @tags @level]
10
+ DEFAULT_INSPECTION_VALUE_PROC = Proc.new{|val| val.inspect}
7
11
 
8
12
  class << self
13
+ attr_writer :inspection_value_proc
14
+
9
15
  def status; 999999 end # Unknown
10
16
  alias_method :code, :status
11
17
  def http_status; 500 end
12
18
  def report?; true end
13
19
  def intentional?; false end
14
20
  def title; _translate('.title') end
21
+ def inspection_vars; @inspection_vars ||= DEFAULT_INSPECTION_VARS.dup end
22
+ def inspection_value_proc; @inspection_value_proc ||= superclass.respond_to?(:inspection_value_proc) ? superclass.inspection_value_proc : DEFAULT_INSPECTION_VALUE_PROC end
23
+ def inspection_value_simple(val)
24
+ case val
25
+ when Array then val.map{|v| inspection_value_simple(v)}
26
+ when Hash then Hash[val.map{|k,v| [k, inspection_value_simple(v)]}]
27
+ when String, Numeric, TrueClass, FalseClass then val
28
+ else val.class.name
29
+ end
30
+ end
15
31
 
16
32
  def before_logging(name, &block)
17
33
  @before_logging_blocks ||= {}
@@ -71,7 +87,7 @@ class StandardError
71
87
  @attributes[:description] = _translate
72
88
  end
73
89
  msg = "#{_translate} (#{msg || self.class.name})"
74
- msg = "#{msg} {#{cause.message}}" if cause
90
+ msg = "#{msg} cause{#{cause.message}}" if cause
75
91
  when String then
76
92
  msg = message
77
93
  when FalseClass, NilClass then
@@ -87,6 +103,15 @@ class StandardError
87
103
  end
88
104
 
89
105
  def safe_message; message || '' end
106
+ def digest_message
107
+ m = message.to_s.dup
108
+ mat = m.match(/#<.*0x(?<object_id>\S+)>/)
109
+ m = message.gsub(/#{mat[:object_id]}/, 'XXXXXXX') if mat
110
+ m = "#{self.class.name} #{m}"
111
+ @digest_message ||= Digest::MD5.hexdigest(m)[0...6]
112
+ end
113
+ def digest_backtrace; @digest_backtrace ||= backtrace ? Digest::MD5.hexdigest(cleaned_backtrace.join("\n"))[0...8] : nil end
114
+ def user_digests; @user_digests ||= "#{[digest_message, digest_backtrace].compact.join(' ')}" end
90
115
  def status; self.class.status end
91
116
  def before_logging_blocks; self.class.before_logging_blocks end
92
117
  def after_logging_blocks; self.class.after_logging_blocks end
@@ -107,6 +132,7 @@ class StandardError
107
132
  def code; attributes[:code] || status end
108
133
  def code=(value); attributes[:code] = value end
109
134
  def title; attributes[:title] || self.class.title end
135
+ def detail; attributes[:detail] end
110
136
  def it_might_happen?; attributes[:it] == :might_happen end
111
137
  def it_should_not_happen?; attributes[:it] == :should_not_happen end
112
138
  def report?
@@ -139,8 +165,9 @@ class StandardError
139
165
  # user friendly message, for overid
140
166
  def user_message
141
167
  return _translate if description.present? || tkey.present?
142
- return "#{_translate} (#{message})" unless defined?(@coaster)
143
- message
168
+ "#{_translate} (#{user_digests})"
169
+ rescue => e
170
+ "#{message} (user_message_error - #{e.class.name} #{e.message})"
144
171
  end
145
172
 
146
173
  # another user friendly messages
@@ -150,52 +177,102 @@ class StandardError
150
177
  attributes[:descriptions]
151
178
  end
152
179
 
153
- def to_hash
154
- hash = attributes.merge(
180
+ def to_hash(_h: {}.with_indifferent_access, _depth: 0)
181
+ _h.merge!(attributes)
182
+ _h.merge!(
155
183
  type: self.class.name, status: status,
156
184
  http_status: http_status, message: message
157
185
  )
158
- if cause
186
+ if _depth < 4 && cause
159
187
  if cause.respond_to?(:to_hash)
160
- hash[:cause] = cause.to_hash
188
+ _h[:cause] = cause.to_hash(_depth: _depth + 1)
161
189
  else
162
- hash[:cause] = cause
190
+ _h[:cause_object] = cause
163
191
  end
164
192
  end
165
- hash
193
+ _h
166
194
  end
167
195
 
168
196
  def to_json
169
197
  Oj.dump(to_hash.with_indifferent_access, mode: :compat)
170
198
  end
171
199
 
172
- def to_detail
173
- lg = "[#{self.class.name}] status:#{status}"
174
- lg += "\n\tMESSAGE: #{safe_message.gsub(/\n/, "\n\t\t")}"
175
- instance_variables.each do |var|
176
- if var.to_s.start_with?('@_') || var.to_s == '@coaster'
177
- next
178
- elsif var.to_s == '@spell_checker'
200
+ def inspection_vars
201
+ (self.class.inspection_vars + (attributes[:inspection_vars] || [])).map(&:to_sym).compact.uniq
202
+ end
203
+
204
+ def inspection_value_proc
205
+ attributes[:inspection_value_proc] || self.class.inspection_value_proc
206
+ end
207
+
208
+ def to_inspection_hash(options: {}, _h: {}.with_indifferent_access, _depth: 0)
209
+ _h.merge!(
210
+ type: self.class.name, status: status,
211
+ http_status: http_status, message: message,
212
+ instance_variables: {}.with_indifferent_access
213
+ )
214
+ instance_variables.sort.each do |var|
215
+ if inspection_vars.include?(var)
216
+ val = instance_variable_get(var)
217
+ val = inspection_value_proc.call(val) rescue val.to_s
218
+ _h[:instance_variables][var] = val
219
+ elsif var.to_s.start_with?('@__')
179
220
  next
180
221
  else
181
222
  val = instance_variable_get(var)
182
- val = to_log_detail_value.call(val) rescue val.to_s
183
- lg += "\n\t#{var}: #{val}"
223
+ _h[:instance_variables][var] = self.class.inspection_value_simple(val)
184
224
  end
185
225
  end
186
- if cause
187
- if cause.respond_to?(:to_detail)
188
- lg += "\n\tCAUSE: "
189
- lg += cause.to_detail.strip.gsub(/\n/, "\n\t")
226
+ if backtrace.present?
227
+ if respond_to?(:cleaned_backtrace)
228
+ if (bt = cleaned_backtrace(options))
229
+ _h[:backtrace] = bt
230
+ else
231
+ _h[:backtrace] = backtrace[0...ActiveSupport::BacktraceCleaner.minimum_first]
232
+ end
190
233
  else
191
- lg += "\n\tCAUSE: #{cause.class.name}: #{cause.message.gsub(/\n/, "\n\t\t")}"
234
+ _h[:backtrace] = backtrace[0...ActiveSupport::BacktraceCleaner.minimum_first]
192
235
  end
193
- if cause_cleaner && cause.backtrace
194
- lg += cause_cleaner.clean(cause.backtrace).join("\n\t\t")
236
+ end
237
+ if cause
238
+ if _depth < 4
239
+ if cause.respond_to?(:to_inspection_hash)
240
+ _h[:cause] = cause.to_inspection_hash(options: options, _depth: _depth + 1)
241
+ else
242
+ cause_h = {
243
+ type: self.class.name, status: status,
244
+ http_status: http_status, message: message,
245
+ }
246
+ cause_h.merge!(backtrace: cause.backtrace[0...ActiveSupport::BacktraceCleaner.minimum_first])
247
+ _h[:cause] = cause_h
248
+ end
249
+ else
250
+ _h[:cause] = 'and more causes...'
195
251
  end
196
252
  end
253
+ _h
254
+ end
255
+
256
+ def to_inspection_s(options: {}, _dh: nil)
257
+ dh = _dh || to_inspection_hash(options: options, _h: {}.with_indifferent_access, _depth: 0)
258
+ lg = "[#{dh[:type]}] status:#{dh[:status]}"
259
+ lg += "\n MESSAGE: #{dh[:message]&.gsub(/\n/, "\n ")}"
260
+ dh[:instance_variables].each do |var, val|
261
+ lg += "\n #{var}: #{val}"
262
+ end
263
+ if (bt = dh[:backtrace] || [])
264
+ lg += "\n BACKTRACE:\n "
265
+ lg += bt.join("\n ")
266
+ end
267
+ if dh[:cause].is_a?(Hash)
268
+ lg += "\n CAUSE: "
269
+ lg += to_inspection_s(options: options, _dh: dh[:cause]).strip.gsub(/\n/, "\n ")
270
+ elsif dh[:cause].is_a?(String)
271
+ lg += dh[:cause]
272
+ end
197
273
  lg << "\n"
198
274
  end
275
+ alias_method :to_detail, :to_inspection_s
199
276
 
200
277
  def rails_tag
201
278
  (fingerprint || Coaster.default_fingerprint).flatten.map do |fp|
@@ -209,8 +286,17 @@ class StandardError
209
286
  end.compact
210
287
  end
211
288
 
289
+ def cleaned_backtrace(options = {})
290
+ return unless backtrace
291
+ cl = options[:cleaner] || cleaner
292
+ return backtrace unless cl
293
+ bt = cl.clean(backtrace)
294
+ bt = bt[0..2] if intentional?
295
+ bt
296
+ end
297
+
212
298
  def logging(options = {})
213
- before_logging_blocks.values.each { |blk| instance_exec &blk }
299
+ before_logging_blocks.values.each { |blk| instance_exec(&blk) }
214
300
 
215
301
  if !report? || intentional?
216
302
  if defined?(Rails)
@@ -223,15 +309,7 @@ class StandardError
223
309
  logger = options[:logger] || Coaster.logger
224
310
  return unless logger
225
311
 
226
- cl = options[:cleaner] || cleaner
227
- msg = to_detail
228
-
229
- if cl && backtrace
230
- bt = cl.clean(backtrace)
231
- bt = bt[0..2] if intentional?
232
- msg += "\tBACKTRACE:\n\t"
233
- msg += bt.join("\n\t")
234
- end
312
+ msg = to_inspection_s(options: options)
235
313
 
236
314
  if level && logger.respond_to?(level)
237
315
  logger.send(level, msg)
@@ -240,6 +318,6 @@ class StandardError
240
318
  end
241
319
  msg
242
320
  ensure
243
- after_logging_blocks.values.each { |blk| instance_exec &blk }
321
+ after_logging_blocks.values.each { |blk| instance_exec(&blk) }
244
322
  end
245
323
  end
@@ -0,0 +1,26 @@
1
+ require 'active_support/backtrace_cleaner'
2
+
3
+ class ActiveSupport::BacktraceCleaner
4
+ attr_writer :minimum_first
5
+
6
+ def minimum_first
7
+ @minimum_first ||= 10
8
+ end
9
+
10
+ private
11
+ alias_method :original_silence, :silence
12
+ def silence(backtrace)
13
+ @silencers.each do |s|
14
+ ix = 0
15
+ backtrace = backtrace.reject do |line|
16
+ ix += 1
17
+ next if ix <= minimum_first
18
+ s.call(line)
19
+ end
20
+ end
21
+
22
+ backtrace = backtrace.to_a
23
+ backtrace.insert(minimum_first, 'BacktraceCleaner.minimum_first ... and next silenced backtraces')
24
+ backtrace
25
+ end
26
+ end
@@ -0,0 +1 @@
1
+ require 'coaster/rails_ext/backtrace_cleaner'
@@ -1,3 +1,3 @@
1
1
  module Coaster
2
- VERSION = '1.3.9'
2
+ VERSION = '1.3.10'
3
3
  end
data/lib/coaster.rb CHANGED
@@ -22,7 +22,7 @@ module Coaster
22
22
  def logger
23
23
  return @@logger if defined?(@@logger) && @@logger
24
24
  return Rails.logger if defined?(Rails)
25
- nil
25
+ @@logger = Logger.new(STDOUT)
26
26
  end
27
27
  end
28
28
 
@@ -32,4 +32,4 @@ module Coaster
32
32
  end
33
33
 
34
34
  require 'coaster/core_ext'
35
- require 'coaster/backtrace_cleaner'
35
+ require 'coaster/rails_ext'
@@ -0,0 +1,182 @@
1
+ require 'test_helper'
2
+ require 'minitest/autorun'
3
+
4
+ module Coaster
5
+ class TestBacktrace < Minitest::Test
6
+ def setup
7
+ @backtrace = <<~EOS.chomp.split("\n")
8
+ /home/circleci/project/app/controllers/application_controller.rb:174:in `block (2 levels) in block_fun'
9
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/metal/mime_responds.rb:214:in `respond_to'
10
+ /home/circleci/project/app/controllers/application_controller.rb:170:in `block_fun'
11
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:427:in `block in make_lambda'
12
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'
13
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
14
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:199:in `block in halting'
15
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:512:in `block in invoke_before'
16
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:512:in `each'
17
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:512:in `invoke_before'
18
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:115:in `block in run_callbacks'
19
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/bundler/gems/vanity-ce67b2c66864/lib/vanity/frameworks/rails.rb:141:in `vanity_context_filter'
20
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
21
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actiontext-6.1.4.4/lib/action_text/rendering.rb:20:in `with_renderer'
22
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actiontext-6.1.4.4/lib/action_text/engine.rb:59:in `block (4 levels) in <class:Engine>'
23
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:126:in `instance_exec'
24
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
25
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/react-rails-2.6.1/lib/react/rails/controller_lifecycle.rb:31:in `use_react_component_helper'
26
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
27
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/callbacks.rb:137:in `run_callbacks'
28
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/abstract_controller/callbacks.rb:41:in `process_action'
29
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/metal/rescue.rb:22:in `process_action'
30
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
31
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/notifications.rb:203:in `block in instrument'
32
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
33
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activesupport-6.1.4.4/lib/active_support/notifications.rb:203:in `instrument'
34
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/metal/instrumentation.rb:33:in `process_action'
35
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/metal/params_wrapper.rb:249:in `process_action'
36
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/searchkick-4.5.2/lib/searchkick/logging.rb:212:in `process_action'
37
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.4/lib/active_record/railties/controller_runtime.rb:27:in `process_action'
38
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/abstract_controller/base.rb:165:in `process'
39
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionview-6.1.4.4/lib/action_view/rendering.rb:39:in `process'
40
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/metal.rb:190:in `dispatch'
41
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/test_case.rb:580:in `process_controller_response'
42
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/test_case.rb:499:in `process'
43
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/actionpack-6.1.4.4/lib/action_controller/test_case.rb:398:in `get'
44
+ /home/circleci/project/spec/controllers/api/funfun_controller_spec.rb:77:in `block (5 levels) in <top (required)>'
45
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:262:in `instance_exec'
46
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:262:in `block in run'
47
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:508:in `block in with_around_and_singleton_context_hooks'
48
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:465:in `block in with_around_example_hooks'
49
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:486:in `block in run'
50
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'
51
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:350:in `call'
52
+ /home/circleci/project/spec/controllers/api/funfun_controller_spec.rb:6:in `block (3 levels) in <top (required)>'
53
+ /home/circleci/project/vendor/gems/funny_gem/lib/funny_gem/locale.rb:272:in `around'
54
+ /home/circleci/project/spec/controllers/api/funfun_controller_spec.rb:5:in `block (2 levels) in <top (required)>'
55
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
56
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
57
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:390:in `execute_with'
58
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'
59
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:350:in `call'
60
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-rails-4.0.2/lib/rspec/rails/example/controller_example_group.rb:191:in `block (2 levels) in <module:ControllerExampleGroup>'
61
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
62
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
63
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:390:in `execute_with'
64
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'
65
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:350:in `call'
66
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-rails-4.0.2/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in <module:MinitestLifecycleAdapter>'
67
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
68
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
69
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:390:in `execute_with'
70
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'
71
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:350:in `call'
72
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/webmock-3.14.0/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
73
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
74
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:455:in `instance_exec'
75
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:390:in `execute_with'
76
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'
77
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:350:in `call'
78
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'
79
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/hooks.rb:486:in `run'
80
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:465:in `with_around_example_hooks'
81
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:508:in `with_around_and_singleton_context_hooks'
82
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example.rb:259:in `run'
83
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:644:in `block in run_examples'
84
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:640:in `map'
85
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:640:in `run_examples'
86
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:606:in `run'
87
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `block in run'
88
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `map'
89
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `run'
90
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `block in run'
91
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `map'
92
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `run'
93
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `block in run'
94
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `map'
95
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/example_group.rb:607:in `run'
96
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'
97
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:121:in `map'
98
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'
99
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/configuration.rb:2067:in `with_suite_hooks'
100
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:116:in `block in run_specs'
101
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/reporter.rb:74:in `report'
102
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:115:in `run_specs'
103
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:89:in `run'
104
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:71:in `run'
105
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/lib/rspec/core/runner.rb:45:in `invoke'
106
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/gems/rspec-core-3.10.1/exe/rspec:4:in `<top (required)>'
107
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
108
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
109
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli/exec.rb:58:in `load'
110
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli/exec.rb:58:in `kernel_load'
111
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli/exec.rb:23:in `run'
112
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli.rb:478:in `exec'
113
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
114
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
115
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
116
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli.rb:31:in `dispatch'
117
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
118
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli.rb:25:in `start'
119
+ /home/circleci/.rubygems/gems/bundler-2.2.30/exe/bundle:49:in `block in <top (required)>'
120
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/friendly_errors.rb:103:in `with_friendly_errors'
121
+ /home/circleci/.rubygems/gems/bundler-2.2.30/exe/bundle:37:in `<top (required)>'
122
+ /home/circleci/.rubygems/bin/bundle:25:in `load'
123
+ /home/circleci/.rubygems/bin/bundle:25:in `<main>'
124
+ EOS
125
+
126
+ @expected_bt = <<~EOS.chomp.split("\n")
127
+ /home/circleci/project/app/controllers/application_controller.rb:174:in `block (2 levels) in block_fun'
128
+ actionpack (6.1.4.4) lib/action_controller/metal/mime_responds.rb:214:in `respond_to'
129
+ /home/circleci/project/app/controllers/application_controller.rb:170:in `block_fun'
130
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:427:in `block in make_lambda'
131
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'
132
+ actionpack (6.1.4.4) lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
133
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:199:in `block in halting'
134
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:512:in `block in invoke_before'
135
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:512:in `each'
136
+ activesupport (6.1.4.4) lib/active_support/callbacks.rb:512:in `invoke_before'
137
+ BacktraceCleaner.minimum_first ... and next silenced backtraces
138
+ /home/circleci/project/spec/controllers/api/funfun_controller_spec.rb:77:in `block (5 levels) in <top (required)>'
139
+ /home/circleci/project/spec/controllers/api/funfun_controller_spec.rb:6:in `block (3 levels) in <top (required)>'
140
+ /home/circleci/project/vendor/gems/funny_gem/lib/funny_gem/locale.rb:272:in `around'
141
+ /home/circleci/project/spec/controllers/api/funfun_controller_spec.rb:5:in `block (2 levels) in <top (required)>'
142
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
143
+ /home/circleci/project/vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
144
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli/exec.rb:58:in `load'
145
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli/exec.rb:58:in `kernel_load'
146
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli/exec.rb:23:in `run'
147
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli.rb:478:in `exec'
148
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
149
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
150
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
151
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli.rb:31:in `dispatch'
152
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
153
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/cli.rb:25:in `start'
154
+ /home/circleci/.rubygems/gems/bundler-2.2.30/exe/bundle:49:in `block in <top (required)>'
155
+ /home/circleci/.rubygems/gems/bundler-2.2.30/lib/bundler/friendly_errors.rb:103:in `with_friendly_errors'
156
+ /home/circleci/.rubygems/gems/bundler-2.2.30/exe/bundle:37:in `<top (required)>'
157
+ /home/circleci/.rubygems/bin/bundle:25:in `load'
158
+ /home/circleci/.rubygems/bin/bundle:25:in `<main>'
159
+ EOS
160
+ end
161
+
162
+ def test_backtrace
163
+ Gem.stub :path, ['/home/circleci/project/vendor/bundle/ruby/2.7.0'] do
164
+ Gem.stub :default_path, [] do
165
+ cleaner = ActiveSupport::BacktraceCleaner.new
166
+ bt = cleaner.clean(@backtrace)
167
+ assert_equal bt, @expected_bt
168
+ end
169
+ end
170
+ end
171
+
172
+ def test_backtrace_with_enumerator
173
+ Gem.stub :path, ['/home/circleci/project/vendor/bundle/ruby/2.7.0'] do
174
+ Gem.stub :default_path, [] do
175
+ cleaner = ActiveSupport::BacktraceCleaner.new
176
+ bt = cleaner.clean(@backtrace.lazy)
177
+ assert_equal bt, @expected_bt
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+ require 'minitest/autorun'
3
+
4
+ module Coaster
5
+ class TestMonth < Minitest::Test
6
+ def test_month
7
+ m = Month.parse('202001')
8
+ assert_equal m.year, 2020
9
+ assert_equal m.last_date, Date.parse('20200131')
10
+ assert_equal m.end_of_month, Date.parse('20200131').end_of_day
11
+ assert_equal m.to_time_range, Date.parse('20200101').beginning_of_day...Date.parse('20200201').beginning_of_day
12
+ end
13
+ end
14
+ end
@@ -2,10 +2,13 @@ require 'test_helper'
2
2
  require 'minitest/autorun'
3
3
  require 'coaster/core_ext/standard_error/raven'
4
4
 
5
- StandardError.to_log_detail_value = Proc.new do |val|
5
+ StandardError.inspection_value_proc = Proc.new do |val|
6
6
  PP.pp(val, ''.dup, 79)[0...-1]
7
7
  end
8
8
 
9
+ StandardError.cleaner = ActiveSupport::BacktraceCleaner.new
10
+ StandardError.cause_cleaner = StandardError.cleaner
11
+
9
12
  module Coaster
10
13
  class TestStandardError < Minitest::Test
11
14
  class SampleError < StandardError
@@ -31,7 +34,7 @@ module Coaster
31
34
  assert_nil e.description
32
35
  assert_nil e.desc
33
36
  assert_equal 'standard error translation', e._translate
34
- assert_equal 'standard error translation (developer message)', e.user_message
37
+ assert_equal 'standard error translation (363fdc)', e.user_message
35
38
  assert_equal 'standard error title', e.title
36
39
  e = StandardError.new(m: 'developer message', desc: 'user message')
37
40
  assert_equal "user message (developer message)", e.to_s
@@ -50,7 +53,7 @@ module Coaster
50
53
  assert_nil e.description
51
54
  assert_nil e.desc
52
55
  assert_equal 'standard error translation', e._translate
53
- assert_equal 'standard error translation (developer message)', e.user_message
56
+ assert_equal 'standard error translation (e39e84)', e.user_message
54
57
  assert_equal 'standard error title', e.title
55
58
  e = UntitledError.new(m: 'developer message', desc: 'user message')
56
59
  assert_equal "user message (developer message)", e.to_s
@@ -77,7 +80,7 @@ module Coaster
77
80
  assert_nil e.description
78
81
  assert_nil e.desc
79
82
  assert_equal 'Test sample error', e._translate
80
- assert_equal 'Test sample error (Coaster::TestStandardError::SampleError)', e.user_message
83
+ assert_equal 'Test sample error (e28ede)', e.user_message
81
84
  assert_equal 'Test this title', e.title
82
85
  e = SampleError.new(beet: 'apple')
83
86
  assert_equal "Test sample error (Coaster::TestStandardError::SampleError)", e.to_s
@@ -85,7 +88,7 @@ module Coaster
85
88
  assert_nil e.description
86
89
  assert_nil e.desc
87
90
  assert_equal 'Test sample error', e._translate
88
- assert_equal 'Test sample error (Coaster::TestStandardError::SampleError)', e.user_message
91
+ assert_equal 'Test sample error (cbe233)', e.user_message
89
92
  assert_equal 'Test this title', e.title
90
93
  e = SampleError.new('developer message')
91
94
  assert_equal "developer message", e.to_s
@@ -93,7 +96,7 @@ module Coaster
93
96
  assert_nil e.description
94
97
  assert_nil e.desc
95
98
  assert_equal 'Test sample error', e._translate
96
- assert_equal 'Test sample error (developer message)', e.user_message
99
+ assert_equal 'Test sample error (43161e)', e.user_message
97
100
  assert_equal 'Test this title', e.title
98
101
  e = SampleError.new(desc: 'user message')
99
102
  assert_equal "user message (Coaster::TestStandardError::SampleError)", e.to_s
@@ -159,7 +162,7 @@ module Coaster
159
162
  assert_equal 'Coaster::TestStandardError::ExampleError', e.to_hash['type']
160
163
  assert_equal 20, e.to_hash['status']
161
164
  assert_equal 500, e.to_hash['http_status']
162
- assert_equal "Test example error (Coaster::TestStandardError::ExampleError) {Test sample error (Coaster::TestStandardError::SampleError)}", e.to_hash['message']
165
+ assert_equal "Test example error (Coaster::TestStandardError::ExampleError) cause{Test sample error (Coaster::TestStandardError::SampleError)}", e.to_hash['message']
163
166
  assert_equal 'rams', e.to_hash['cause']['frog']
164
167
  assert_equal 'Coaster::TestStandardError::SampleError', e.to_hash['cause']['type']
165
168
  assert_equal 10, e.to_hash['cause']['status']
@@ -174,7 +177,7 @@ module Coaster
174
177
  raise ExampleError, {m: 'abc', wat: 'cha'}
175
178
  end
176
179
  rescue => e
177
- assert_equal 'Test example error (abc) {Test sample error (Coaster::TestStandardError::SampleError)}', e.message
180
+ assert_equal 'Test example error (abc) cause{Test sample error (Coaster::TestStandardError::SampleError)}', e.message
178
181
  assert_equal 'rams', e.cause.attr['frog']
179
182
  assert_equal 'rams', e.attr['frog']
180
183
  assert_equal 'cha', e.attr['wat']
@@ -184,28 +187,73 @@ module Coaster
184
187
  begin
185
188
  raise SampleError, {frog: 'rams'}
186
189
  rescue => e
187
- raise ExampleError, {wat: 'cha'}
190
+ err = ExampleError.new(wat: 'cha')
191
+ err.instance_variable_set(:@ins_var, [SampleError.new, {h: 1}])
192
+ err.instance_variable_set(:@ins_varr, {dd: true})
193
+ raise err
188
194
  end
189
195
  rescue => e
190
- detail = <<-LOG
196
+ detail = e.to_inspection_s
197
+ detail_front = <<-LOG
191
198
  [Coaster::TestStandardError::ExampleError] status:20
192
- MESSAGE: Test example error (Coaster::TestStandardError::ExampleError) {Test sample error (Coaster::TestStandardError::SampleError)}
193
- @fingerprint: []
194
- @tags: {}
195
- @level: \"error\"
196
- @attributes: {\"frog\"=>\"rams\", \"wat\"=>\"cha\"}
197
- @tkey: nil
198
- @raven: {}
199
- CAUSE: [Coaster::TestStandardError::SampleError] status:10
200
- MESSAGE: Test sample error (Coaster::TestStandardError::SampleError)
201
- @fingerprint: []
202
- @tags: {}
203
- @level: \"error\"
204
- @attributes: {\"frog\"=>\"rams\"}
205
- @tkey: nil
206
- @raven: {}
199
+ MESSAGE: Test example error (Coaster::TestStandardError::ExampleError) cause{Test sample error (Coaster::TestStandardError::SampleError)}
200
+ @attributes: {\"frog\"=>\"rams\", \"wat\"=>\"cha\"}
201
+ @coaster: true
202
+ @fingerprint: []
203
+ @ins_var: [\"Coaster::TestStandardError::SampleError\", {\"h\"=>1}]
204
+ @ins_varr: {\"dd\"=>true}
205
+ @level: \"error\"
206
+ @raven: {}
207
+ @tags: {}
208
+ @tkey: nil
209
+ BACKTRACE:
210
+ #{__FILE__}:193:in `rescue in test_to_detail'
211
+ #{__FILE__}:187:in `test_to_detail'
212
+ LOG
213
+ detail_cause_front = <<-LOG
214
+ CAUSE: [Coaster::TestStandardError::SampleError] status:10
215
+ MESSAGE: Test sample error (Coaster::TestStandardError::SampleError)
216
+ @attributes: {"frog"=>"rams"}
217
+ @coaster: true
218
+ @fingerprint: []
219
+ @level: "error"
220
+ @raven: {}
221
+ @tags: {}
222
+ @tkey: nil
223
+ BACKTRACE:
224
+ #{__FILE__}:188:in `test_to_detail'
207
225
  LOG
208
- assert_equal(detail, e.to_detail)
226
+ assert detail.start_with?(detail_front)
227
+ cause_ix = (detail =~ /CAUSE/)
228
+ cause = detail[cause_ix..-1]
229
+ assert cause.start_with?(detail_cause_front)
230
+ end
231
+
232
+ def test_to_detail_with_depth
233
+ begin
234
+ begin
235
+ begin
236
+ begin
237
+ begin
238
+ raise SampleError
239
+ rescue => e
240
+ raise SampleError
241
+ end
242
+ rescue => e
243
+ raise SampleError
244
+ end
245
+ rescue => e
246
+ raise SampleError
247
+ end
248
+ rescue => e
249
+ raise SampleError
250
+ end
251
+ rescue => e
252
+ raise SampleError
253
+ end
254
+ rescue => e
255
+ detail = e.to_inspection_s
256
+ assert detail =~ /and more causes/
209
257
  end
210
258
 
211
259
  def test_translation
@@ -263,7 +311,8 @@ LOG
263
311
  assert_equal 'NameError', e.to_hash['type']
264
312
  assert_equal 999999, e.to_hash['status']
265
313
  assert_equal 500, e.to_hash['http_status']
266
- assert_match /undefined local variable or method `aa'/, e.to_hash['message']
314
+ assert_equal 'standard error translation (a962bd 80dfafa3)', e.user_message
315
+ assert_match(/undefined local variable or method `aa'/, e.to_hash['message'])
267
316
  end
268
317
 
269
318
  def test_descriptions
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coaster
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.9
4
+ version: 1.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - buzz jung
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-28 00:00:00.000000000 Z
11
+ date: 2022-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -146,7 +146,6 @@ files:
146
146
  - README.md
147
147
  - Rakefile
148
148
  - lib/coaster.rb
149
- - lib/coaster/backtrace_cleaner.rb
150
149
  - lib/coaster/core_ext.rb
151
150
  - lib/coaster/core_ext/array.rb
152
151
  - lib/coaster/core_ext/date.rb
@@ -156,10 +155,14 @@ files:
156
155
  - lib/coaster/core_ext/standard_error.rb
157
156
  - lib/coaster/core_ext/standard_error/raven.rb
158
157
  - lib/coaster/core_ext/standard_error/sentry.rb
158
+ - lib/coaster/rails_ext.rb
159
+ - lib/coaster/rails_ext/backtrace_cleaner.rb
159
160
  - lib/coaster/serialized_properties.rb
160
161
  - lib/coaster/version.rb
161
162
  - test/locales/en.yml
163
+ - test/test_backtrace.rb
162
164
  - test/test_helper.rb
165
+ - test/test_month.rb
163
166
  - test/test_object_translation.rb
164
167
  - test/test_standard_error.rb
165
168
  homepage: http://github.com/frograms/coaster
@@ -181,12 +184,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
184
  - !ruby/object:Gem::Version
182
185
  version: '0'
183
186
  requirements: []
184
- rubygems_version: 3.2.32
187
+ rubygems_version: 3.3.7
185
188
  signing_key:
186
189
  specification_version: 4
187
190
  summary: A little convenient feature for standard library
188
191
  test_files:
189
192
  - test/locales/en.yml
193
+ - test/test_backtrace.rb
190
194
  - test/test_helper.rb
195
+ - test/test_month.rb
191
196
  - test/test_object_translation.rb
192
197
  - test/test_standard_error.rb
@@ -1,78 +0,0 @@
1
- class Coaster::BacktraceCleaner
2
- attr_accessor :least
3
-
4
- def initialize
5
- @filters, @silencers = [], []
6
- @least = 10
7
- end
8
-
9
- # Returns the backtrace after all filters and silencers have been run
10
- # against it. Filters run first, then silencers.
11
- def clean(backtrace, kind = :silent)
12
- filtered = filter_backtrace(backtrace)
13
-
14
- case kind
15
- when :silent
16
- silence(filtered)
17
- when :noise
18
- noise(filtered)
19
- else
20
- filtered
21
- end
22
- end
23
- alias :filter :clean
24
-
25
- # Adds a filter from the block provided. Each line in the backtrace will be
26
- # mapped against this filter.
27
- #
28
- # # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
29
- # backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
30
- def add_filter(&block)
31
- @filters << block
32
- end
33
-
34
- # Adds a silencer from the block provided. If the silencer returns +true+
35
- # for a given line, it will be excluded from the clean backtrace.
36
- #
37
- # # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
38
- # backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
39
- def add_silencer(&block)
40
- @silencers << block
41
- end
42
-
43
- # Removes all silencers, but leaves in the filters. Useful if your
44
- # context of debugging suddenly expands as you suspect a bug in one of
45
- # the libraries you use.
46
- def remove_silencers!
47
- @silencers = []
48
- end
49
-
50
- # Removes all filters, but leaves in the silencers. Useful if you suddenly
51
- # need to see entire filepaths in the backtrace that you had already
52
- # filtered out.
53
- def remove_filters!
54
- @filters = []
55
- end
56
-
57
- private
58
- def filter_backtrace(backtrace)
59
- @filters.each do |f|
60
- backtrace = backtrace.map { |line| f.call(line) }
61
- end
62
-
63
- backtrace
64
- end
65
-
66
- def silence(backtrace)
67
- least_bt = backtrace.shift(least)
68
- @silencers.each do |s|
69
- backtrace = backtrace.reject { |line| s.call(line) }
70
- end
71
-
72
- least_bt + backtrace
73
- end
74
-
75
- def noise(backtrace)
76
- backtrace - silence(backtrace)
77
- end
78
- end