coaster 1.3.7 → 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: 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