appsignal 4.5.16 → 4.6.0

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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -0
  3. data/CLAUDE.md +177 -0
  4. data/README.md +17 -0
  5. data/appsignal.gemspec +1 -0
  6. data/build_matrix.yml +29 -0
  7. data/lib/appsignal/auth_check.rb +3 -3
  8. data/lib/appsignal/check_in/cron.rb +1 -1
  9. data/lib/appsignal/check_in/event.rb +1 -1
  10. data/lib/appsignal/check_in/scheduler.rb +3 -1
  11. data/lib/appsignal/check_in.rb +9 -6
  12. data/lib/appsignal/cli/diagnose.rb +1 -1
  13. data/lib/appsignal/cli.rb +1 -1
  14. data/lib/appsignal/config.rb +231 -30
  15. data/lib/appsignal/custom_marker.rb +3 -3
  16. data/lib/appsignal/environment.rb +7 -1
  17. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +1 -1
  18. data/lib/appsignal/event_formatter/active_record/instantiation_formatter.rb +1 -1
  19. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +1 -1
  20. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +1 -1
  21. data/lib/appsignal/event_formatter/faraday/request_formatter.rb +1 -1
  22. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +1 -1
  23. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -1
  24. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +1 -1
  25. data/lib/appsignal/event_formatter/view_component/render_formatter.rb +1 -1
  26. data/lib/appsignal/event_formatter.rb +41 -3
  27. data/lib/appsignal/extension.rb +1 -1
  28. data/lib/appsignal/garbage_collection.rb +1 -1
  29. data/lib/appsignal/helpers/instrumentation.rb +82 -35
  30. data/lib/appsignal/helpers/metrics.rb +12 -9
  31. data/lib/appsignal/hooks/action_cable.rb +1 -1
  32. data/lib/appsignal/hooks/action_mailer.rb +1 -0
  33. data/lib/appsignal/hooks/active_job.rb +1 -1
  34. data/lib/appsignal/hooks/active_support_notifications.rb +1 -1
  35. data/lib/appsignal/hooks/at_exit.rb +1 -1
  36. data/lib/appsignal/hooks/celluloid.rb +1 -1
  37. data/lib/appsignal/hooks/data_mapper.rb +1 -1
  38. data/lib/appsignal/hooks/delayed_job.rb +1 -1
  39. data/lib/appsignal/hooks/dry_monitor.rb +1 -1
  40. data/lib/appsignal/hooks/excon.rb +1 -1
  41. data/lib/appsignal/hooks/gvl.rb +1 -1
  42. data/lib/appsignal/hooks/http.rb +1 -1
  43. data/lib/appsignal/hooks/mongo_ruby_driver.rb +1 -1
  44. data/lib/appsignal/hooks/mri.rb +1 -1
  45. data/lib/appsignal/hooks/net_http.rb +1 -1
  46. data/lib/appsignal/hooks/ownership.rb +1 -1
  47. data/lib/appsignal/hooks/passenger.rb +1 -1
  48. data/lib/appsignal/hooks/puma.rb +1 -1
  49. data/lib/appsignal/hooks/que.rb +1 -1
  50. data/lib/appsignal/hooks/rake.rb +1 -1
  51. data/lib/appsignal/hooks/redis.rb +1 -1
  52. data/lib/appsignal/hooks/redis_client.rb +1 -1
  53. data/lib/appsignal/hooks/resque.rb +1 -1
  54. data/lib/appsignal/hooks/sequel.rb +2 -1
  55. data/lib/appsignal/hooks/shoryuken.rb +1 -1
  56. data/lib/appsignal/hooks/sidekiq.rb +1 -0
  57. data/lib/appsignal/hooks/unicorn.rb +1 -1
  58. data/lib/appsignal/hooks/webmachine.rb +1 -1
  59. data/lib/appsignal/hooks.rb +5 -3
  60. data/lib/appsignal/integrations/action_cable.rb +1 -1
  61. data/lib/appsignal/integrations/active_support_notifications.rb +1 -1
  62. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +1 -1
  63. data/lib/appsignal/integrations/data_mapper.rb +1 -1
  64. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  65. data/lib/appsignal/integrations/dry_monitor.rb +1 -1
  66. data/lib/appsignal/integrations/excon.rb +1 -1
  67. data/lib/appsignal/integrations/http.rb +1 -1
  68. data/lib/appsignal/integrations/mongo_ruby_driver.rb +1 -1
  69. data/lib/appsignal/integrations/net_http.rb +1 -1
  70. data/lib/appsignal/integrations/object.rb +18 -2
  71. data/lib/appsignal/integrations/ownership.rb +1 -1
  72. data/lib/appsignal/integrations/puma.rb +1 -1
  73. data/lib/appsignal/integrations/que.rb +1 -1
  74. data/lib/appsignal/integrations/railtie.rb +2 -2
  75. data/lib/appsignal/integrations/rake.rb +2 -2
  76. data/lib/appsignal/integrations/redis.rb +1 -1
  77. data/lib/appsignal/integrations/redis_client.rb +1 -1
  78. data/lib/appsignal/integrations/resque.rb +2 -2
  79. data/lib/appsignal/integrations/shoryuken.rb +1 -1
  80. data/lib/appsignal/integrations/sidekiq.rb +3 -3
  81. data/lib/appsignal/integrations/unicorn.rb +1 -1
  82. data/lib/appsignal/integrations/webmachine.rb +1 -1
  83. data/lib/appsignal/internal_errors.rb +2 -2
  84. data/lib/appsignal/loaders.rb +1 -1
  85. data/lib/appsignal/logger.rb +120 -79
  86. data/lib/appsignal/marker.rb +1 -1
  87. data/lib/appsignal/probes/gvl.rb +4 -3
  88. data/lib/appsignal/probes/helpers.rb +1 -1
  89. data/lib/appsignal/probes/mri.rb +3 -3
  90. data/lib/appsignal/probes/sidekiq.rb +4 -3
  91. data/lib/appsignal/probes.rb +20 -15
  92. data/lib/appsignal/rack/body_wrapper.rb +1 -1
  93. data/lib/appsignal/rack.rb +1 -1
  94. data/lib/appsignal/sample_data.rb +31 -12
  95. data/lib/appsignal/span.rb +1 -1
  96. data/lib/appsignal/system.rb +9 -8
  97. data/lib/appsignal/transaction.rb +72 -52
  98. data/lib/appsignal/transmitter.rb +1 -1
  99. data/lib/appsignal/utils.rb +1 -1
  100. data/lib/appsignal/version.rb +2 -1
  101. data/lib/appsignal.rb +22 -14
  102. data/lib/puma/plugin/appsignal.rb +1 -1
  103. data/sig/appsignal.rbi +2599 -0
  104. data/sig/appsignal.rbs +2420 -0
  105. metadata +20 -6
@@ -9,9 +9,50 @@ module Appsignal
9
9
  # @see https://docs.appsignal.com/logging/platforms/integrations/ruby.html
10
10
  # AppSignal Ruby logging documentation.
11
11
  class Logger < ::Logger
12
+ # A wrapper for a block that ensures it is only called once.
13
+ # If called again, it will return the result of the first call.
14
+ # This is useful for ensuring that a block is not executed multiple
15
+ # times when it is broadcasted to multiple loggers.
16
+ #
17
+ # @!visibility private
18
+ class BlockOnce
19
+ def initialize(&block)
20
+ @block = block
21
+ @called = false
22
+ @success = nil
23
+ @result = nil
24
+ @error = nil
25
+ end
26
+
27
+ def call(*args, **kwargs)
28
+ if @called
29
+ return @result if @success
30
+
31
+ raise @error
32
+ end
33
+
34
+ @called = true
35
+ @result = @block.call(*args, **kwargs)
36
+ @success = true
37
+ @result
38
+ rescue StandardError => e
39
+ @success = false
40
+ @error = e
41
+ raise @error
42
+ end
43
+
44
+ def to_proc
45
+ method(:call).to_proc
46
+ end
47
+ end
48
+
49
+ # @!visibility private
12
50
  PLAINTEXT = 0
51
+ # @!visibility private
13
52
  LOGFMT = 1
53
+ # @!visibility private
14
54
  JSON = 2
55
+ # @!visibility private
15
56
  SEVERITY_MAP = {
16
57
  DEBUG => 2,
17
58
  INFO => 3,
@@ -20,30 +61,34 @@ module Appsignal
20
61
  FATAL => 7
21
62
  }.freeze
22
63
 
64
+ # Logging severity threshold
65
+ # @return [Integer]
23
66
  attr_reader :level
24
67
 
25
68
  # Create a new logger instance
26
69
  #
27
- # @param group Name of the group for this logger.
28
- # @param level Minimum log level to report. Log lines below this level will be ignored.
29
- # @param format Format to use to parse log line attributes.
30
- # @param attributes Default attributes for all log lines.
70
+ # @param group [String] Name of the group for this logger.
71
+ # @param level [Integer] Minimum log level to report. Log lines below this
72
+ # level will be ignored.
73
+ # @param format [Integer] Format to use to parse log line attributes.
74
+ # @param attributes [Hash<String, String>] Default attributes for all log lines.
31
75
  # @return [void]
32
76
  def initialize(group, level: INFO, format: PLAINTEXT, attributes: {})
33
77
  raise TypeError, "group must be a string" unless group.is_a? String
34
78
 
35
79
  @group = group
36
80
  @level = level
81
+ @silenced = false
37
82
  @format = format
38
83
  @mutex = Mutex.new
39
84
  @default_attributes = attributes
40
- @appsignal_attributes = {}
85
+ @appsignal_attributes = attributes
41
86
  @loggers = []
42
87
  end
43
88
 
44
- # When a formatter is set on the logger (e.g. when wrapping the logger in
45
- # `ActiveSupport::TaggedLogging`) we want to set that formatter on all the
46
- # loggers that are being broadcasted to.
89
+ # Sets the formatter for this logger and all broadcasted loggers.
90
+ # @param formatter [Proc] The formatter to use for log messages.
91
+ # @return [Proc]
47
92
  def formatter=(formatter)
48
93
  super
49
94
  @loggers.each { |logger| logger.formatter = formatter }
@@ -51,33 +96,44 @@ module Appsignal
51
96
 
52
97
  # We support the various methods in the Ruby
53
98
  # logger class by supplying this method.
54
- # @api private
55
- def add(severity, message = nil, group = nil)
99
+ # @!visibility private
100
+ def add(severity, message = nil, group = nil, &block) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
101
+ # If we do not need to broadcast to any loggers and the severity is
102
+ # below the log level, we can return early.
56
103
  severity ||= UNKNOWN
57
- return true if severity < level
104
+ return true if severity < level && @loggers.empty?
105
+
106
+ # If the logger is silenced, we do not log *or broadcast* messages
107
+ # below the log level.
108
+ return true if @silenced && severity < @level
109
+
110
+ # Ensure that the block is only run once, even if several loggers
111
+ # are being broadcasted to.
112
+ block = BlockOnce.new(&block) unless block.nil?
58
113
 
114
+ # If the group is not set, we use the default group.
59
115
  group = @group if group.nil?
60
- if message.nil?
61
- if block_given?
62
- message = yield
63
- else
64
- message = group
65
- group = @group
66
- end
67
- end
68
- return if message.nil?
116
+
117
+ did_not_log = true
69
118
 
70
119
  @loggers.each do |logger|
71
- logger.add(severity, message, group)
120
+ # Loggers should return true if they did *not* log the message.
121
+ # If any of the broadcasted loggers logs the message, that counts
122
+ # as having logged the message.
123
+ did_not_log &&= logger.add(severity, message, group, &block)
72
124
  rescue
73
125
  nil
74
126
  end
75
127
 
76
- unless message.is_a?(String)
77
- Appsignal.internal_logger.warn(
78
- "Logger message was ignored, because it was not a String: #{message.inspect}"
79
- )
80
- return
128
+ # If the severity is below the log level, we do not log the message.
129
+ return did_not_log if severity < level
130
+
131
+ message = block.call if block && message.nil?
132
+
133
+ return if message.nil?
134
+
135
+ if message.is_a?(Exception)
136
+ message = "#{message.class}: #{message.message} (#{message.backtrace[0]})"
81
137
  end
82
138
 
83
139
  message = formatter.call(severity, Time.now, group, message) if formatter
@@ -86,90 +142,67 @@ module Appsignal
86
142
  group,
87
143
  SEVERITY_MAP.fetch(severity, 0),
88
144
  @format,
89
- message,
145
+ message.to_s,
90
146
  Appsignal::Utils::Data.generate(appsignal_attributes)
91
147
  )
148
+
149
+ false
92
150
  end
93
151
  alias log add
94
152
 
95
153
  # Log a debug level message
96
- # @param message Message to log
97
- # @param attributes Attributes to tag the log with
154
+ # @param message [String] Message to log
155
+ # @param attributes [Hash<String, Object>] Attributes to tag the log with
98
156
  # @return [void]
99
- def debug(message = nil, attributes = {})
100
- return if level > DEBUG
101
-
102
- message = yield if message.nil? && block_given?
103
- return if message.nil?
104
-
105
- add_with_attributes(DEBUG, message, @group, attributes)
157
+ def debug(message = nil, attributes = {}, &block)
158
+ add_with_attributes(DEBUG, message, @group, attributes, &block)
106
159
  end
107
160
 
108
161
  # Log an info level message
109
- # @param message Message to log
110
- # @param attributes Attributes to tag the log with
162
+ # @param message [String] Message to log
163
+ # @param attributes [Hash<String, Object>] Attributes to tag the log with
111
164
  # @return [void]
112
- def info(message = nil, attributes = {})
113
- return if level > INFO
114
-
115
- message = yield if message.nil? && block_given?
116
- return if message.nil?
117
-
118
- add_with_attributes(INFO, message, @group, attributes)
165
+ def info(message = nil, attributes = {}, &block)
166
+ add_with_attributes(INFO, message, @group, attributes, &block)
119
167
  end
120
168
 
121
169
  # Log a warn level message
122
- # @param message Message to log
123
- # @param attributes Attributes to tag the log with
170
+ # @param message [String] Message to log
171
+ # @param attributes [Hash<String, Object>] Attributes to tag the log with
124
172
  # @return [void]
125
- def warn(message = nil, attributes = {})
126
- return if level > WARN
127
-
128
- message = yield if message.nil? && block_given?
129
- return if message.nil?
130
-
131
- add_with_attributes(WARN, message, @group, attributes)
173
+ def warn(message = nil, attributes = {}, &block)
174
+ add_with_attributes(WARN, message, @group, attributes, &block)
132
175
  end
133
176
 
134
177
  # Log an error level message
135
- # @param message Message to log
136
- # @param attributes Attributes to tag the log with
178
+ # @param message [String, Exception] Message to log
179
+ # @param attributes [Hash<String, Object>] Attributes to tag the log with
137
180
  # @return [void]
138
- def error(message = nil, attributes = {})
139
- return if level > ERROR
140
-
141
- message = yield if message.nil? && block_given?
142
- return if message.nil?
143
-
144
- message = "#{message.class}: #{message.message}" if message.is_a?(Exception)
145
-
146
- add_with_attributes(ERROR, message, @group, attributes)
181
+ def error(message = nil, attributes = {}, &block)
182
+ add_with_attributes(ERROR, message, @group, attributes, &block)
147
183
  end
148
184
 
149
185
  # Log a fatal level message
150
- # @param message Message to log
151
- # @param attributes Attributes to tag the log with
186
+ # @param message [String, Exception] Message to log
187
+ # @param attributes [Hash<String, Object>] Attributes to tag the log with
152
188
  # @return [void]
153
- def fatal(message = nil, attributes = {})
154
- return if level > FATAL
155
-
156
- message = yield if message.nil? && block_given?
157
- return if message.nil?
158
-
159
- add_with_attributes(FATAL, message, @group, attributes)
189
+ def fatal(message = nil, attributes = {}, &block)
190
+ add_with_attributes(FATAL, message, @group, attributes, &block)
160
191
  end
161
192
 
162
193
  # Log an info level message
163
194
  #
164
195
  # Returns the number of characters written.
165
196
  #
166
- # @param message Message to log
197
+ # @param message [String] Message to log
167
198
  # @return [Integer]
168
199
  def <<(message)
169
- add(Logger::INFO, message)
200
+ info(message)
170
201
  message.length
171
202
  end
172
203
 
204
+ # Temporarily silences the logger to a specified level while executing a block.
205
+ #
173
206
  # When using ActiveSupport::TaggedLogging without the broadcast feature,
174
207
  # the passed logger is required to respond to the `silence` method.
175
208
  #
@@ -177,14 +210,22 @@ module Appsignal
177
210
  #
178
211
  # - https://github.com/rails/rails/blob/e11ebc04cfbe41c06cdfb70ee5a9fdbbd98bb263/activesupport/lib/active_support/logger.rb#L60-L76
179
212
  # - https://github.com/rails/rails/blob/e11ebc04cfbe41c06cdfb70ee5a9fdbbd98bb263/activesupport/lib/active_support/logger_silence.rb
213
+ #
214
+ # @param severity [Integer] The minimum severity level to log during the block.
215
+ # @return [Object] The return value of the block.
180
216
  def silence(severity = ERROR, &block)
181
217
  previous_level = @level
182
218
  @level = severity
219
+ @silenced = true
183
220
  block.call(self)
184
221
  ensure
185
222
  @level = previous_level
223
+ @silenced = false
186
224
  end
187
225
 
226
+ # Adds a logger to broadcast log messages to.
227
+ # @param logger [Logger] The logger to add to the broadcast list.
228
+ # @return [Array<Logger>]
188
229
  def broadcast_to(logger)
189
230
  @loggers << logger
190
231
  end
@@ -193,11 +234,11 @@ module Appsignal
193
234
 
194
235
  attr_reader :default_attributes, :appsignal_attributes
195
236
 
196
- def add_with_attributes(severity, message, group, attributes)
237
+ def add_with_attributes(severity, message, group, attributes, &block)
197
238
  @appsignal_attributes = default_attributes.merge(attributes)
198
- add(severity, message, group)
239
+ add(severity, message, group, &block)
199
240
  ensure
200
- @appsignal_attributes = {}
241
+ @appsignal_attributes = default_attributes
201
242
  end
202
243
  end
203
244
  end
@@ -20,7 +20,7 @@ module Appsignal
20
20
  # @see Appsignal::CLI::NotifyOfDeploy
21
21
  # @see https://docs.appsignal.com/appsignal/terminology.html#markers
22
22
  # Terminology: Deploy marker
23
- # @api private
23
+ # @!visibility private
24
24
  class Marker
25
25
  # Path used on the AppSignal Push API
26
26
  # https://push.appsignal.com/1/markers
@@ -2,21 +2,22 @@
2
2
 
3
3
  module Appsignal
4
4
  module Probes
5
+ # @!visibility private
5
6
  class GvlProbe
6
7
  include Helpers
7
8
 
8
- # @api private
9
+ # @!visibility private
9
10
  def self.dependencies_present?
10
11
  defined?(::GVLTools) && gvltools_0_2_or_newer? && ruby_3_2_or_newer? &&
11
12
  !Appsignal::System.jruby?
12
13
  end
13
14
 
14
- # @api private
15
+ # @!visibility private
15
16
  def self.gvltools_0_2_or_newer?
16
17
  Gem::Version.new(::GVLTools::VERSION) >= Gem::Version.new("0.2.0")
17
18
  end
18
19
 
19
- # @api private
20
+ # @!visibility private
20
21
  def self.ruby_3_2_or_newer?
21
22
  Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2.0")
22
23
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Appsignal
4
4
  module Probes
5
- # @api private
5
+ # @!visibility private
6
6
  module Helpers
7
7
  private
8
8
 
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Appsignal
4
4
  module Probes
5
- # @api private
5
+ # @!visibility private
6
6
  class MriProbe
7
7
  include Helpers
8
8
 
9
- # @api private
9
+ # @!visibility private
10
10
  def self.dependencies_present?
11
11
  defined?(::RubyVM) && ::RubyVM.respond_to?(:stat)
12
12
  end
@@ -17,7 +17,7 @@ module Appsignal
17
17
  @gc_profiler = gc_profiler
18
18
  end
19
19
 
20
- # @api private
20
+ # @!visibility private
21
21
  def call
22
22
  stat = RubyVM.stat
23
23
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Appsignal
4
4
  module Probes
5
- # @api private
5
+ # @!visibility private
6
6
  class SidekiqProbe
7
7
  include Helpers
8
8
 
@@ -38,14 +38,15 @@ module Appsignal
38
38
  end
39
39
  end
40
40
 
41
- # @api private
41
+ # @!visibility private
42
42
  attr_reader :config
43
43
 
44
+ # @!visibility private
44
45
  def self.sidekiq7_and_greater?
45
46
  Gem::Version.new(::Sidekiq::VERSION) >= Gem::Version.new("7.0.0")
46
47
  end
47
48
 
48
- # @api private
49
+ # @!visibility private
49
50
  def self.dependencies_present?
50
51
  return true if sidekiq7_and_greater?
51
52
  return false unless defined?(::Redis::VERSION) # Sidekiq <= 6
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Appsignal
4
4
  module Probes
5
- # @api private
5
+ # @return [Integer]
6
+ # @!visibility private
6
7
  ITERATION_IN_SECONDS = 60
7
8
 
8
- # @api private
9
+ # @!visibility private
9
10
  class ProbeCollection
10
11
  def initialize
11
12
  @probes = {}
@@ -23,13 +24,12 @@ module Appsignal
23
24
  end
24
25
 
25
26
  # Fetch a probe using its name.
26
- # @param key [Symbol/String] The name of the probe to fetch.
27
+ # @param key [Symbol, String] The name of the probe to fetch.
27
28
  # @return [Object] Returns the registered probe.
28
29
  def [](key)
29
30
  probes[key]
30
31
  end
31
32
 
32
- # @api private
33
33
  def internal_register(name, probe)
34
34
  if probes.key?(name)
35
35
  logger.debug "A probe with the name `#{name}` is already " \
@@ -38,12 +38,10 @@ module Appsignal
38
38
  probes[name] = probe
39
39
  end
40
40
 
41
- # @api private
42
41
  def unregister(name)
43
42
  probes.delete(name)
44
43
  end
45
44
 
46
- # @api private
47
45
  def each(&block)
48
46
  probes.each(&block)
49
47
  end
@@ -58,14 +56,14 @@ module Appsignal
58
56
  end
59
57
 
60
58
  class << self
61
- # @api private
59
+ # @!visibility private
62
60
  def mutex
63
61
  @mutex ||= Thread::Mutex.new
64
62
  end
65
63
 
66
64
  # @see ProbeCollection
67
65
  # @return [ProbeCollection] Returns list of probes.
68
- # @api private
66
+ # @!visibility private
69
67
  def probes
70
68
  @probes ||= ProbeCollection.new
71
69
  end
@@ -129,7 +127,7 @@ module Appsignal
129
127
  # # "started" # Printed on Appsignal::Probes.start
130
128
  # # "called" # Repeated every minute
131
129
  #
132
- # @param name [Symbol/String] Name of the probe. Can be used with
130
+ # @param name [Symbol, String] Name of the probe. Can be used with
133
131
  # {ProbeCollection#[]}. This name will be used in errors in the log and
134
132
  # allows overwriting of probes by registering new ones with the same
135
133
  # name.
@@ -153,7 +151,7 @@ module Appsignal
153
151
  # # Then unregister a probe if needed
154
152
  # Appsignal::Probes.unregister :my_probe
155
153
  #
156
- # @param name [Symbol/String] Name of the probe used to {register} the
154
+ # @param name [Symbol, String] Name of the probe used to {register} the
157
155
  # probe.
158
156
  # @return [void]
159
157
  def unregister(name)
@@ -162,6 +160,7 @@ module Appsignal
162
160
  uninitialize_probe(name)
163
161
  end
164
162
 
163
+ # @return [void]
165
164
  # @api private
166
165
  def start
167
166
  stop
@@ -184,8 +183,10 @@ module Appsignal
184
183
  logger.debug("Gathering minutely metrics with '#{name}' probe")
185
184
  probe.call
186
185
  rescue => ex
187
- logger.error "Error in minutely probe '#{name}': #{ex}"
188
- logger.debug ex.backtrace.join("\n")
186
+ logger.error(
187
+ "Error in minutely probe '#{name}': #{ex.class}: #{ex.message}\n" \
188
+ "#{ex.backtrace.join("\n")}"
189
+ )
189
190
  end
190
191
  end
191
192
  end_time = Time.now
@@ -212,13 +213,15 @@ module Appsignal
212
213
 
213
214
  # Stop the minutely probes mechanism. Stop the thread and clear all probe
214
215
  # instances.
216
+ #
217
+ # @return [void]
215
218
  def stop
216
219
  defined?(@thread) && @thread.kill
217
220
  @started = false
218
221
  probe_instances.clear
219
222
  end
220
223
 
221
- # @api private
224
+ # @!visibility private
222
225
  def wait_time
223
226
  ITERATION_IN_SECONDS - Time.now.sec
224
227
  end
@@ -256,8 +259,10 @@ module Appsignal
256
259
  end
257
260
  rescue => error
258
261
  logger = Appsignal.internal_logger
259
- logger.error "Error while initializing minutely probe '#{name}': #{error}"
260
- logger.debug error.backtrace.join("\n")
262
+ logger.error(
263
+ "Error while initializing minutely probe '#{name}': #{error.class}: #{error.message}\n" \
264
+ "#{error.backtrace.join("\n")}"
265
+ )
261
266
  end
262
267
 
263
268
  def uninitialize_probe(name)
@@ -4,7 +4,7 @@ module Appsignal
4
4
  module Rack
5
5
  # @api private
6
6
  class BodyWrapper
7
- IGNORED_ERRORS = [Errno::EPIPE].freeze
7
+ IGNORED_ERRORS = [Errno::EPIPE, Errno::ECONNRESET].freeze
8
8
 
9
9
  def self.wrap(original_body, appsignal_transaction)
10
10
  # The logic of how Rack treats a response body differs based on which methods
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- # @api private
4
+ # @!visibility private
5
5
  module Rack
6
6
  APPSIGNAL_TRANSACTION = "appsignal.transaction"
7
7
  APPSIGNAL_EVENT_HANDLER_ID = "appsignal.event_handler_id"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- # @api private
4
+ # @!visibility private
5
5
  class SampleData
6
6
  def initialize(key, accepted_type = nil)
7
7
  @key = key
@@ -21,7 +21,6 @@ module Appsignal
21
21
  end
22
22
  end
23
23
 
24
- # @api private
25
24
  def set_empty_value!
26
25
  @empty = true
27
26
  @blocks.clear
@@ -36,12 +35,18 @@ module Appsignal
36
35
  else
37
36
  block_or_value
38
37
  end
38
+
39
39
  unless accepted_type?(new_value)
40
40
  log_unsupported_data_type(new_value)
41
41
  next
42
42
  end
43
43
 
44
- value = merge_values(value, new_value)
44
+ # Before trying to merge values, convert them to Ruby classes
45
+ # This way we don't need to check if something is of a type often
46
+ value_new = convert_to_ruby_class(new_value)
47
+ value_original = convert_to_ruby_class(value)
48
+
49
+ value = merge_values(value_original, value_new)
45
50
  new_value
46
51
  end
47
52
 
@@ -52,7 +57,6 @@ module Appsignal
52
57
  @blocks.any?
53
58
  end
54
59
 
55
- # @api private
56
60
  def empty?
57
61
  @empty
58
62
  end
@@ -81,15 +85,19 @@ module Appsignal
81
85
  end
82
86
  end
83
87
 
88
+ def mergable?(value_original, value_new)
89
+ value_new.instance_of?(value_original.class)
90
+ end
91
+
84
92
  def merge_values(value_original, value_new)
85
- unless value_new.instance_of?(value_original.class)
86
- unless value_original == UNSET_VALUE
87
- Appsignal.internal_logger.warn(
88
- "The sample data '#{@key}' changed type from " \
89
- "'#{value_original.class}' to '#{value_new.class}'. " \
90
- "These types can not be merged. Using new '#{value_new.class}' type."
91
- )
92
- end
93
+ return value_new if value_original == UNSET_VALUE
94
+
95
+ unless mergable?(value_original, value_new)
96
+ Appsignal.internal_logger.warn(
97
+ "The sample data '#{@key}' changed type from " \
98
+ "'#{value_original.class}' to '#{value_new.class}'. " \
99
+ "These types can not be merged. Using new '#{value_new.class}' type."
100
+ )
93
101
  return value_new
94
102
  end
95
103
 
@@ -103,6 +111,17 @@ module Appsignal
103
111
  end
104
112
  end
105
113
 
114
+ # Convert any subclasses of Hash to a Ruby Hash class, so we don't have to
115
+ # account for the original value being a value that's not a pure Hash or
116
+ # Array object.
117
+ def convert_to_ruby_class(value)
118
+ if value.is_a? Hash
119
+ value.to_h
120
+ else
121
+ value
122
+ end
123
+ end
124
+
106
125
  def log_unsupported_data_type(value)
107
126
  Appsignal.internal_logger.error(
108
127
  "Sample data '#{@key}': Unsupported data type '#{value.class}' received: #{value.inspect}"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- # @api private
4
+ # @!visibility private
5
5
  class Span
6
6
  def initialize(namespace = nil, ext = nil)
7
7
  @ext = ext || Appsignal::Extension::Span.root(namespace || "")