coaster 1.3.9 → 1.3.12

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