coaster 1.3.7 → 1.3.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03f82d0ec684919dbdd18695f9c9b44accf45defa3260b7fcfeaadaf511b5869
4
- data.tar.gz: 963dd53ba23fe26a009308b6065d25d58ec0a32713980479840f86d4fd71ed71
3
+ metadata.gz: 5b2e4ea7b170f104949852f0413f81dac320102b8045377554d8fcc8034004fd
4
+ data.tar.gz: d20610716bddf3fada3e41f6b9762738ce0eaa3aca7072e5b612d93539d10d0c
5
5
  SHA512:
6
- metadata.gz: ce52bb11a0aa053e521ff7261b079ae47e424846ecb7937596112cb5ec2a42b56a0f13afddb222a1c1bc333685f94c9fc97478865067fd44b1ce9eb5596515be
7
- data.tar.gz: 7b6278654542c48270b3c817d1bf15f404da41c0bf36d546f9f7dac2f229a322c119fe0760690acd7011511433808a8fa228e8d62b53300cb21fc0bb9a3af9a9
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
@@ -44,7 +46,7 @@ class StandardError
44
46
  scope.user.merge!(nt[:user] || {})
45
47
  scope.tags.merge!(nt[:tags])
46
48
  scope.extra.merge!(nt[:extra])
47
- scope.fingerprint = raven_fingerprint
49
+ scope.set_fingerprint(raven_fingerprint)
48
50
  end
49
51
  rescue => e
50
52
  msg = "#{e.class.name}: #{e.message}"
@@ -1,15 +1,33 @@
1
+ require 'digest'
1
2
  require 'coaster/core_ext/object_translation'
3
+ require 'coaster/rails_ext/backtrace_cleaner'
4
+ require 'pp'
2
5
 
3
6
  class StandardError
4
7
  cattr_accessor :cleaner, :cause_cleaner
5
8
 
9
+ DEFAULT_INSPECTION_VARS = %i[@attributes @tkey @fingerprint @tags @level]
10
+ DEFAULT_INSPECTION_VALUE_PROC = Proc.new{|val| val.inspect}
11
+
6
12
  class << self
13
+ attr_writer :inspection_value_proc
14
+
7
15
  def status; 999999 end # Unknown
8
16
  alias_method :code, :status
9
17
  def http_status; 500 end
10
18
  def report?; true end
11
19
  def intentional?; false end
12
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
13
31
 
14
32
  def before_logging(name, &block)
15
33
  @before_logging_blocks ||= {}
@@ -69,7 +87,7 @@ class StandardError
69
87
  @attributes[:description] = _translate
70
88
  end
71
89
  msg = "#{_translate} (#{msg || self.class.name})"
72
- msg = "#{msg} {#{cause.message}}" if cause
90
+ msg = "#{msg} cause{#{cause.message}}" if cause
73
91
  when String then
74
92
  msg = message
75
93
  when FalseClass, NilClass then
@@ -85,6 +103,15 @@ class StandardError
85
103
  end
86
104
 
87
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
88
115
  def status; self.class.status end
89
116
  def before_logging_blocks; self.class.before_logging_blocks end
90
117
  def after_logging_blocks; self.class.after_logging_blocks end
@@ -105,6 +132,7 @@ class StandardError
105
132
  def code; attributes[:code] || status end
106
133
  def code=(value); attributes[:code] = value end
107
134
  def title; attributes[:title] || self.class.title end
135
+ def detail; attributes[:detail] end
108
136
  def it_might_happen?; attributes[:it] == :might_happen end
109
137
  def it_should_not_happen?; attributes[:it] == :should_not_happen end
110
138
  def report?
@@ -137,8 +165,9 @@ class StandardError
137
165
  # user friendly message, for overid
138
166
  def user_message
139
167
  return _translate if description.present? || tkey.present?
140
- return "#{_translate} (#{message})" unless defined?(@coaster)
141
- message
168
+ "#{_translate} (#{user_digests})"
169
+ rescue => e
170
+ "#{message} (user_message_error - #{e.class.name} #{e.message})"
142
171
  end
143
172
 
144
173
  # another user friendly messages
@@ -148,52 +177,102 @@ class StandardError
148
177
  attributes[:descriptions]
149
178
  end
150
179
 
151
- def to_hash
152
- hash = attributes.merge(
180
+ def to_hash(_h: {}.with_indifferent_access, _depth: 0)
181
+ _h.merge!(attributes)
182
+ _h.merge!(
153
183
  type: self.class.name, status: status,
154
184
  http_status: http_status, message: message
155
185
  )
156
- if cause
186
+ if _depth < 4 && cause
157
187
  if cause.respond_to?(:to_hash)
158
- hash[:cause] = cause.to_hash
188
+ _h[:cause] = cause.to_hash(_depth: _depth + 1)
159
189
  else
160
- hash[:cause] = cause
190
+ _h[:cause_object] = cause
161
191
  end
162
192
  end
163
- hash
193
+ _h
164
194
  end
165
195
 
166
196
  def to_json
167
197
  Oj.dump(to_hash.with_indifferent_access, mode: :compat)
168
198
  end
169
199
 
170
- def to_detail
171
- lg = "[#{self.class.name}] status:#{status}"
172
- lg += "\n\tMESSAGE: #{safe_message.gsub(/\n/, "\n\t\t")}"
173
- instance_variables.each do |var|
174
- if var.to_s.start_with?('@_') || var.to_s == '@coaster'
175
- next
176
- 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?('@__')
177
220
  next
178
221
  else
179
222
  val = instance_variable_get(var)
180
- val = val.inspect rescue val.to_s
181
- lg += "\n\t#{var}: #{val}"
223
+ _h[:instance_variables][var] = self.class.inspection_value_simple(val)
182
224
  end
183
225
  end
184
- if cause
185
- if cause.respond_to?(:to_detail)
186
- lg += "\n\tCAUSE: "
187
- 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
188
233
  else
189
- lg += "\n\tCAUSE: #{cause.class.name}: #{cause.message.gsub(/\n/, "\n\t\t")}"
234
+ _h[:backtrace] = backtrace[0...ActiveSupport::BacktraceCleaner.minimum_first]
190
235
  end
191
- if cause_cleaner && cause.backtrace
192
- 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...'
193
251
  end
194
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
195
273
  lg << "\n"
196
274
  end
275
+ alias_method :to_detail, :to_inspection_s
197
276
 
198
277
  def rails_tag
199
278
  (fingerprint || Coaster.default_fingerprint).flatten.map do |fp|
@@ -207,8 +286,17 @@ class StandardError
207
286
  end.compact
208
287
  end
209
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
+
210
298
  def logging(options = {})
211
- before_logging_blocks.values.each { |blk| instance_exec &blk }
299
+ before_logging_blocks.values.each { |blk| instance_exec(&blk) }
212
300
 
213
301
  if !report? || intentional?
214
302
  if defined?(Rails)
@@ -221,15 +309,7 @@ class StandardError
221
309
  logger = options[:logger] || Coaster.logger
222
310
  return unless logger
223
311
 
224
- cl = options[:cleaner] || cleaner
225
- msg = to_detail
226
-
227
- if cl && backtrace
228
- bt = cl.clean(backtrace)
229
- bt = bt[0..2] if intentional?
230
- msg += "\tBACKTRACE:\n\t"
231
- msg += bt.join("\n\t")
232
- end
312
+ msg = to_inspection_s(options: options)
233
313
 
234
314
  if level && logger.respond_to?(level)
235
315
  logger.send(level, msg)
@@ -238,6 +318,6 @@ class StandardError
238
318
  end
239
319
  msg
240
320
  ensure
241
- after_logging_blocks.values.each { |blk| instance_exec &blk }
321
+ after_logging_blocks.values.each { |blk| instance_exec(&blk) }
242
322
  end
243
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.7'
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,6 +2,13 @@ require 'test_helper'
2
2
  require 'minitest/autorun'
3
3
  require 'coaster/core_ext/standard_error/raven'
4
4
 
5
+ StandardError.inspection_value_proc = Proc.new do |val|
6
+ PP.pp(val, ''.dup, 79)[0...-1]
7
+ end
8
+
9
+ StandardError.cleaner = ActiveSupport::BacktraceCleaner.new
10
+ StandardError.cause_cleaner = StandardError.cleaner
11
+
5
12
  module Coaster
6
13
  class TestStandardError < Minitest::Test
7
14
  class SampleError < StandardError
@@ -27,7 +34,7 @@ module Coaster
27
34
  assert_nil e.description
28
35
  assert_nil e.desc
29
36
  assert_equal 'standard error translation', e._translate
30
- assert_equal 'standard error translation (developer message)', e.user_message
37
+ assert_equal 'standard error translation (363fdc)', e.user_message
31
38
  assert_equal 'standard error title', e.title
32
39
  e = StandardError.new(m: 'developer message', desc: 'user message')
33
40
  assert_equal "user message (developer message)", e.to_s
@@ -46,7 +53,7 @@ module Coaster
46
53
  assert_nil e.description
47
54
  assert_nil e.desc
48
55
  assert_equal 'standard error translation', e._translate
49
- assert_equal 'standard error translation (developer message)', e.user_message
56
+ assert_equal 'standard error translation (e39e84)', e.user_message
50
57
  assert_equal 'standard error title', e.title
51
58
  e = UntitledError.new(m: 'developer message', desc: 'user message')
52
59
  assert_equal "user message (developer message)", e.to_s
@@ -73,7 +80,7 @@ module Coaster
73
80
  assert_nil e.description
74
81
  assert_nil e.desc
75
82
  assert_equal 'Test sample error', e._translate
76
- assert_equal 'Test sample error (Coaster::TestStandardError::SampleError)', e.user_message
83
+ assert_equal 'Test sample error (e28ede)', e.user_message
77
84
  assert_equal 'Test this title', e.title
78
85
  e = SampleError.new(beet: 'apple')
79
86
  assert_equal "Test sample error (Coaster::TestStandardError::SampleError)", e.to_s
@@ -81,7 +88,7 @@ module Coaster
81
88
  assert_nil e.description
82
89
  assert_nil e.desc
83
90
  assert_equal 'Test sample error', e._translate
84
- assert_equal 'Test sample error (Coaster::TestStandardError::SampleError)', e.user_message
91
+ assert_equal 'Test sample error (cbe233)', e.user_message
85
92
  assert_equal 'Test this title', e.title
86
93
  e = SampleError.new('developer message')
87
94
  assert_equal "developer message", e.to_s
@@ -89,7 +96,7 @@ module Coaster
89
96
  assert_nil e.description
90
97
  assert_nil e.desc
91
98
  assert_equal 'Test sample error', e._translate
92
- assert_equal 'Test sample error (developer message)', e.user_message
99
+ assert_equal 'Test sample error (43161e)', e.user_message
93
100
  assert_equal 'Test this title', e.title
94
101
  e = SampleError.new(desc: 'user message')
95
102
  assert_equal "user message (Coaster::TestStandardError::SampleError)", e.to_s
@@ -155,7 +162,7 @@ module Coaster
155
162
  assert_equal 'Coaster::TestStandardError::ExampleError', e.to_hash['type']
156
163
  assert_equal 20, e.to_hash['status']
157
164
  assert_equal 500, e.to_hash['http_status']
158
- 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']
159
166
  assert_equal 'rams', e.to_hash['cause']['frog']
160
167
  assert_equal 'Coaster::TestStandardError::SampleError', e.to_hash['cause']['type']
161
168
  assert_equal 10, e.to_hash['cause']['status']
@@ -170,7 +177,7 @@ module Coaster
170
177
  raise ExampleError, {m: 'abc', wat: 'cha'}
171
178
  end
172
179
  rescue => e
173
- 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
174
181
  assert_equal 'rams', e.cause.attr['frog']
175
182
  assert_equal 'rams', e.attr['frog']
176
183
  assert_equal 'cha', e.attr['wat']
@@ -180,28 +187,73 @@ module Coaster
180
187
  begin
181
188
  raise SampleError, {frog: 'rams'}
182
189
  rescue => e
183
- 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
184
194
  end
185
195
  rescue => e
186
- detail = <<-LOG
196
+ detail = e.to_inspection_s
197
+ detail_front = <<-LOG
187
198
  [Coaster::TestStandardError::ExampleError] status:20
188
- MESSAGE: Test example error (Coaster::TestStandardError::ExampleError) {Test sample error (Coaster::TestStandardError::SampleError)}
189
- @fingerprint: []
190
- @tags: {}
191
- @level: \"error\"
192
- @attributes: {\"frog\"=>\"rams\", \"wat\"=>\"cha\"}
193
- @tkey: nil
194
- @raven: {}
195
- CAUSE: [Coaster::TestStandardError::SampleError] status:10
196
- MESSAGE: Test sample error (Coaster::TestStandardError::SampleError)
197
- @fingerprint: []
198
- @tags: {}
199
- @level: \"error\"
200
- @attributes: {\"frog\"=>\"rams\"}
201
- @tkey: nil
202
- @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'
203
225
  LOG
204
- 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/
205
257
  end
206
258
 
207
259
  def test_translation
@@ -259,7 +311,8 @@ LOG
259
311
  assert_equal 'NameError', e.to_hash['type']
260
312
  assert_equal 999999, e.to_hash['status']
261
313
  assert_equal 500, e.to_hash['http_status']
262
- 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'])
263
316
  end
264
317
 
265
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.7
4
+ version: 1.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - buzz jung
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-03 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,17 +155,21 @@ 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
166
169
  licenses:
167
170
  - MIT
168
171
  metadata: {}
169
- post_install_message:
172
+ post_install_message:
170
173
  rdoc_options: []
171
174
  require_paths:
172
175
  - lib
@@ -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.1.6
185
- signing_key:
187
+ rubygems_version: 3.3.7
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
190
- - test/test_standard_error.rb
193
+ - test/test_backtrace.rb
191
194
  - test/test_helper.rb
195
+ - test/test_month.rb
192
196
  - test/test_object_translation.rb
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