appsignal 4.5.17-java → 4.7.0-java
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 +4 -4
- data/CHANGELOG.md +62 -0
- data/CLAUDE.md +192 -0
- data/README.md +17 -0
- data/appsignal.gemspec +1 -0
- data/build_matrix.yml +29 -0
- data/lib/appsignal/auth_check.rb +3 -3
- data/lib/appsignal/check_in/cron.rb +1 -1
- data/lib/appsignal/check_in/event.rb +1 -1
- data/lib/appsignal/check_in/scheduler.rb +3 -1
- data/lib/appsignal/check_in.rb +9 -6
- data/lib/appsignal/cli/diagnose.rb +1 -1
- data/lib/appsignal/cli.rb +1 -1
- data/lib/appsignal/config.rb +231 -30
- data/lib/appsignal/custom_marker.rb +3 -3
- data/lib/appsignal/environment.rb +7 -1
- data/lib/appsignal/event_formatter/action_view/render_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/active_record/instantiation_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/faraday/request_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +1 -1
- data/lib/appsignal/event_formatter/view_component/render_formatter.rb +1 -1
- data/lib/appsignal/event_formatter.rb +41 -3
- data/lib/appsignal/extension.rb +1 -1
- data/lib/appsignal/garbage_collection.rb +1 -1
- data/lib/appsignal/helpers/instrumentation.rb +82 -35
- data/lib/appsignal/helpers/metrics.rb +12 -9
- data/lib/appsignal/hooks/action_cable.rb +1 -1
- data/lib/appsignal/hooks/action_mailer.rb +1 -0
- data/lib/appsignal/hooks/active_job.rb +1 -1
- data/lib/appsignal/hooks/active_support_notifications.rb +1 -1
- data/lib/appsignal/hooks/at_exit.rb +1 -1
- data/lib/appsignal/hooks/celluloid.rb +1 -1
- data/lib/appsignal/hooks/data_mapper.rb +1 -1
- data/lib/appsignal/hooks/delayed_job.rb +1 -1
- data/lib/appsignal/hooks/dry_monitor.rb +1 -1
- data/lib/appsignal/hooks/excon.rb +1 -1
- data/lib/appsignal/hooks/gvl.rb +1 -1
- data/lib/appsignal/hooks/http.rb +1 -1
- data/lib/appsignal/hooks/mongo_ruby_driver.rb +1 -1
- data/lib/appsignal/hooks/mri.rb +1 -1
- data/lib/appsignal/hooks/net_http.rb +1 -1
- data/lib/appsignal/hooks/ownership.rb +1 -1
- data/lib/appsignal/hooks/passenger.rb +1 -1
- data/lib/appsignal/hooks/puma.rb +1 -1
- data/lib/appsignal/hooks/que.rb +1 -1
- data/lib/appsignal/hooks/rake.rb +1 -1
- data/lib/appsignal/hooks/redis.rb +1 -1
- data/lib/appsignal/hooks/redis_client.rb +1 -1
- data/lib/appsignal/hooks/resque.rb +1 -1
- data/lib/appsignal/hooks/sequel.rb +2 -1
- data/lib/appsignal/hooks/shoryuken.rb +1 -1
- data/lib/appsignal/hooks/sidekiq.rb +1 -0
- data/lib/appsignal/hooks/unicorn.rb +1 -1
- data/lib/appsignal/hooks/webmachine.rb +1 -1
- data/lib/appsignal/hooks.rb +5 -3
- data/lib/appsignal/integrations/action_cable.rb +1 -1
- data/lib/appsignal/integrations/active_support_notifications.rb +1 -1
- data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +1 -1
- data/lib/appsignal/integrations/data_mapper.rb +1 -1
- data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
- data/lib/appsignal/integrations/dry_monitor.rb +1 -1
- data/lib/appsignal/integrations/excon.rb +1 -1
- data/lib/appsignal/integrations/http.rb +1 -1
- data/lib/appsignal/integrations/mongo_ruby_driver.rb +1 -1
- data/lib/appsignal/integrations/net_http.rb +1 -1
- data/lib/appsignal/integrations/object.rb +18 -2
- data/lib/appsignal/integrations/ownership.rb +1 -1
- data/lib/appsignal/integrations/puma.rb +1 -1
- data/lib/appsignal/integrations/que.rb +1 -1
- data/lib/appsignal/integrations/railtie.rb +17 -8
- data/lib/appsignal/integrations/rake.rb +2 -2
- data/lib/appsignal/integrations/redis.rb +1 -1
- data/lib/appsignal/integrations/redis_client.rb +1 -1
- data/lib/appsignal/integrations/resque.rb +2 -2
- data/lib/appsignal/integrations/shoryuken.rb +1 -1
- data/lib/appsignal/integrations/sidekiq.rb +15 -4
- data/lib/appsignal/integrations/unicorn.rb +1 -1
- data/lib/appsignal/integrations/webmachine.rb +1 -1
- data/lib/appsignal/internal_errors.rb +2 -2
- data/lib/appsignal/loaders/hanami.rb +1 -4
- data/lib/appsignal/loaders/padrino.rb +1 -1
- data/lib/appsignal/loaders/sinatra.rb +1 -1
- data/lib/appsignal/loaders.rb +1 -1
- data/lib/appsignal/logger.rb +36 -19
- data/lib/appsignal/marker.rb +1 -1
- data/lib/appsignal/probes/gvl.rb +4 -3
- data/lib/appsignal/probes/helpers.rb +1 -1
- data/lib/appsignal/probes/mri.rb +3 -3
- data/lib/appsignal/probes/sidekiq.rb +4 -3
- data/lib/appsignal/probes.rb +20 -15
- data/lib/appsignal/rack/event_handler.rb +21 -3
- data/lib/appsignal/rack/event_middleware.rb +111 -0
- data/lib/appsignal/rack/instrumentation_middleware.rb +2 -2
- data/lib/appsignal/rack.rb +1 -1
- data/lib/appsignal/sample_data.rb +31 -12
- data/lib/appsignal/span.rb +1 -1
- data/lib/appsignal/system.rb +9 -8
- data/lib/appsignal/transaction.rb +72 -52
- data/lib/appsignal/transmitter.rb +1 -1
- data/lib/appsignal/utils.rb +1 -1
- data/lib/appsignal/version.rb +2 -1
- data/lib/appsignal.rb +23 -14
- data/lib/puma/plugin/appsignal.rb +1 -1
- data/sig/appsignal.rbi +2599 -0
- data/sig/appsignal.rbs +2420 -0
- metadata +20 -2
data/lib/appsignal/logger.rb
CHANGED
@@ -13,6 +13,8 @@ module Appsignal
|
|
13
13
|
# If called again, it will return the result of the first call.
|
14
14
|
# This is useful for ensuring that a block is not executed multiple
|
15
15
|
# times when it is broadcasted to multiple loggers.
|
16
|
+
#
|
17
|
+
# @!visibility private
|
16
18
|
class BlockOnce
|
17
19
|
def initialize(&block)
|
18
20
|
@block = block
|
@@ -44,9 +46,13 @@ module Appsignal
|
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
49
|
+
# @!visibility private
|
47
50
|
PLAINTEXT = 0
|
51
|
+
# @!visibility private
|
48
52
|
LOGFMT = 1
|
53
|
+
# @!visibility private
|
49
54
|
JSON = 2
|
55
|
+
# @!visibility private
|
50
56
|
SEVERITY_MAP = {
|
51
57
|
DEBUG => 2,
|
52
58
|
INFO => 3,
|
@@ -55,14 +61,17 @@ module Appsignal
|
|
55
61
|
FATAL => 7
|
56
62
|
}.freeze
|
57
63
|
|
64
|
+
# Logging severity threshold
|
65
|
+
# @return [Integer]
|
58
66
|
attr_reader :level
|
59
67
|
|
60
68
|
# Create a new logger instance
|
61
69
|
#
|
62
|
-
# @param group Name of the group for this logger.
|
63
|
-
# @param level Minimum log level to report. Log lines below this
|
64
|
-
#
|
65
|
-
# @param
|
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.
|
66
75
|
# @return [void]
|
67
76
|
def initialize(group, level: INFO, format: PLAINTEXT, attributes: {})
|
68
77
|
raise TypeError, "group must be a string" unless group.is_a? String
|
@@ -77,9 +86,9 @@ module Appsignal
|
|
77
86
|
@loggers = []
|
78
87
|
end
|
79
88
|
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
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]
|
83
92
|
def formatter=(formatter)
|
84
93
|
super
|
85
94
|
@loggers.each { |logger| logger.formatter = formatter }
|
@@ -87,7 +96,7 @@ module Appsignal
|
|
87
96
|
|
88
97
|
# We support the various methods in the Ruby
|
89
98
|
# logger class by supplying this method.
|
90
|
-
#
|
99
|
+
# @!visibility private
|
91
100
|
def add(severity, message = nil, group = nil, &block) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
92
101
|
# If we do not need to broadcast to any loggers and the severity is
|
93
102
|
# below the log level, we can return early.
|
@@ -142,40 +151,40 @@ module Appsignal
|
|
142
151
|
alias log add
|
143
152
|
|
144
153
|
# Log a debug level message
|
145
|
-
# @param message Message to log
|
146
|
-
# @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
|
147
156
|
# @return [void]
|
148
157
|
def debug(message = nil, attributes = {}, &block)
|
149
158
|
add_with_attributes(DEBUG, message, @group, attributes, &block)
|
150
159
|
end
|
151
160
|
|
152
161
|
# Log an info level message
|
153
|
-
# @param message Message to log
|
154
|
-
# @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
|
155
164
|
# @return [void]
|
156
165
|
def info(message = nil, attributes = {}, &block)
|
157
166
|
add_with_attributes(INFO, message, @group, attributes, &block)
|
158
167
|
end
|
159
168
|
|
160
169
|
# Log a warn level message
|
161
|
-
# @param message Message to log
|
162
|
-
# @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
|
163
172
|
# @return [void]
|
164
173
|
def warn(message = nil, attributes = {}, &block)
|
165
174
|
add_with_attributes(WARN, message, @group, attributes, &block)
|
166
175
|
end
|
167
176
|
|
168
177
|
# Log an error level message
|
169
|
-
# @param message Message to log
|
170
|
-
# @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
|
171
180
|
# @return [void]
|
172
181
|
def error(message = nil, attributes = {}, &block)
|
173
182
|
add_with_attributes(ERROR, message, @group, attributes, &block)
|
174
183
|
end
|
175
184
|
|
176
185
|
# Log a fatal level message
|
177
|
-
# @param message Message to log
|
178
|
-
# @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
|
179
188
|
# @return [void]
|
180
189
|
def fatal(message = nil, attributes = {}, &block)
|
181
190
|
add_with_attributes(FATAL, message, @group, attributes, &block)
|
@@ -185,13 +194,15 @@ module Appsignal
|
|
185
194
|
#
|
186
195
|
# Returns the number of characters written.
|
187
196
|
#
|
188
|
-
# @param message Message to log
|
197
|
+
# @param message [String] Message to log
|
189
198
|
# @return [Integer]
|
190
199
|
def <<(message)
|
191
200
|
info(message)
|
192
201
|
message.length
|
193
202
|
end
|
194
203
|
|
204
|
+
# Temporarily silences the logger to a specified level while executing a block.
|
205
|
+
#
|
195
206
|
# When using ActiveSupport::TaggedLogging without the broadcast feature,
|
196
207
|
# the passed logger is required to respond to the `silence` method.
|
197
208
|
#
|
@@ -199,6 +210,9 @@ module Appsignal
|
|
199
210
|
#
|
200
211
|
# - https://github.com/rails/rails/blob/e11ebc04cfbe41c06cdfb70ee5a9fdbbd98bb263/activesupport/lib/active_support/logger.rb#L60-L76
|
201
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.
|
202
216
|
def silence(severity = ERROR, &block)
|
203
217
|
previous_level = @level
|
204
218
|
@level = severity
|
@@ -209,6 +223,9 @@ module Appsignal
|
|
209
223
|
@silenced = false
|
210
224
|
end
|
211
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>]
|
212
229
|
def broadcast_to(logger)
|
213
230
|
@loggers << logger
|
214
231
|
end
|
data/lib/appsignal/marker.rb
CHANGED
@@ -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
|
-
#
|
23
|
+
# @!visibility private
|
24
24
|
class Marker
|
25
25
|
# Path used on the AppSignal Push API
|
26
26
|
# https://push.appsignal.com/1/markers
|
data/lib/appsignal/probes/gvl.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
data/lib/appsignal/probes/mri.rb
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Appsignal
|
4
4
|
module Probes
|
5
|
-
#
|
5
|
+
# @!visibility private
|
6
6
|
class MriProbe
|
7
7
|
include Helpers
|
8
8
|
|
9
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
data/lib/appsignal/probes.rb
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
module Appsignal
|
4
4
|
module Probes
|
5
|
-
# @
|
5
|
+
# @return [Integer]
|
6
|
+
# @!visibility private
|
6
7
|
ITERATION_IN_SECONDS = 60
|
7
8
|
|
8
|
-
#
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
188
|
-
|
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
|
-
#
|
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
|
260
|
-
|
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)
|
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
module Appsignal
|
4
4
|
module Rack
|
5
|
-
# Instrumentation
|
5
|
+
# Instrumentation event handler for `Rack::Events`.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
# {
|
7
|
+
# Using this middleware directly with `Rack::Events` is deprecated.
|
8
|
+
# We recommend using {EventMiddleware} instead, which is compatible with
|
9
|
+
# streaming bodies, in combination with the {InstrumentationMiddleware}.
|
9
10
|
#
|
10
11
|
# This middleware will report the response status code as the
|
11
12
|
# `response_status` tag on the sample. It will also report the response
|
@@ -38,10 +39,12 @@ module Appsignal
|
|
38
39
|
|
39
40
|
# @api private
|
40
41
|
attr_reader :id
|
42
|
+
attr_writer :using_appsignal_rack_events_middleware
|
41
43
|
|
42
44
|
# @api private
|
43
45
|
def initialize
|
44
46
|
@id = SecureRandom.uuid
|
47
|
+
@using_appsignal_rack_events_middleware = false
|
45
48
|
end
|
46
49
|
|
47
50
|
# @api private
|
@@ -51,6 +54,8 @@ module Appsignal
|
|
51
54
|
|
52
55
|
# @api private
|
53
56
|
def on_start(request, _response)
|
57
|
+
emit_warning_once
|
58
|
+
|
54
59
|
return unless Appsignal.active?
|
55
60
|
|
56
61
|
event_handler = self
|
@@ -152,6 +157,19 @@ module Appsignal
|
|
152
157
|
namespace
|
153
158
|
end
|
154
159
|
end
|
160
|
+
|
161
|
+
def emit_warning_once
|
162
|
+
return if @using_appsignal_rack_events_middleware
|
163
|
+
|
164
|
+
Appsignal.internal_logger.warn <<~MSG
|
165
|
+
Rack::Events is not compatible with streaming bodies. Using `Appsignal::Rack::EventHandler`#{" "}
|
166
|
+
with `Rack::Events` will break streaming responses in your application and is deprecated.#{" "}
|
167
|
+
To silence this warning, use `Appsignal::Rack::EventMiddleware` instead, which is compatible#{" "}
|
168
|
+
with streaming bodies.#{" "}
|
169
|
+
See https://docs.appsignal.com/ruby/integrations/rack.html for more information.
|
170
|
+
MSG
|
171
|
+
@using_appsignal_rack_events_middleware = true
|
172
|
+
end
|
155
173
|
end
|
156
174
|
end
|
157
175
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
module Rack
|
5
|
+
# Modified version of the {::Rack::Events} instrumentation
|
6
|
+
# middleware.
|
7
|
+
#
|
8
|
+
# We recommend using this instead of {::Rack::Events}, as it
|
9
|
+
# is compatible with streaming bodies.
|
10
|
+
#
|
11
|
+
# We do not recommend using this middleware directly, instead
|
12
|
+
# recommending the use of {EventMiddleware}, which is a
|
13
|
+
# convenience wrapper around this middleware that includes
|
14
|
+
# AppSignal's {EventHandler}.
|
15
|
+
#
|
16
|
+
# See the original implementation at:
|
17
|
+
# https://github.com/rack/rack/blob/8d3d7857fcd9e5df057a6c22458bab35b3a19c12/lib/rack/events.rb
|
18
|
+
class Events < ::Rack::Events
|
19
|
+
# A stub for {::Rack::Events::EventedBodyProxy}. It
|
20
|
+
# allows the same initialization arguments, but
|
21
|
+
# otherwise behaves identically to {::Rack::BodyProxy}.
|
22
|
+
#
|
23
|
+
# It does not implement `#each`, fixing an issue
|
24
|
+
# where the evented body proxy would break
|
25
|
+
# streaming responses by always responding to `#each`
|
26
|
+
# even if the proxied body did not implement it.
|
27
|
+
#
|
28
|
+
# Because it ignores the handlers passed to it and
|
29
|
+
# behaves like a normal body proxy, the `on_send`
|
30
|
+
# event on the handlers is never called.
|
31
|
+
class EventedBodyProxy < ::Rack::BodyProxy
|
32
|
+
def initialize(body, _request, _response, _handlers, &block)
|
33
|
+
super(body, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Notifies the AppSignal event handler that it's being
|
38
|
+
# used with the modified middleware, suppressing a
|
39
|
+
# deprecation warning.
|
40
|
+
def initialize(app, handlers = [])
|
41
|
+
super
|
42
|
+
handlers.each do |handler|
|
43
|
+
if handler.respond_to?(:using_appsignal_rack_events_middleware=)
|
44
|
+
handler.using_appsignal_rack_events_middleware = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# The `call` method, exactly as implemented by {::Rack::Events},
|
50
|
+
# but redefined here so that it uses our {EventedBodyProxy}
|
51
|
+
# instead of the original {::Rack::Events::EventedBodyProxy}.
|
52
|
+
#
|
53
|
+
# This fixes streaming bodies, but it also means that the
|
54
|
+
# `on_send` event on the handlers is never called.
|
55
|
+
#
|
56
|
+
# See the original implementation at:
|
57
|
+
# https://github.com/rack/rack/blob/8d3d7857fcd9e5df057a6c22458bab35b3a19c12/lib/rack/events.rb#L111-L129
|
58
|
+
def call(env)
|
59
|
+
request = make_request env
|
60
|
+
on_start request, nil
|
61
|
+
|
62
|
+
begin
|
63
|
+
status, headers, body = @app.call request.env
|
64
|
+
response = make_response status, headers, body
|
65
|
+
on_commit request, response
|
66
|
+
rescue StandardError => e
|
67
|
+
on_error request, response, e
|
68
|
+
on_finish request, response
|
69
|
+
raise
|
70
|
+
end
|
71
|
+
|
72
|
+
body = EventedBodyProxy.new(body, request, response, @handlers) do
|
73
|
+
on_finish request, response
|
74
|
+
end
|
75
|
+
[response.status, response.headers, body]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Instrumentation middleware using Rack's Events module.
|
80
|
+
#
|
81
|
+
# A convenience wrapper around our {Events} middleware,
|
82
|
+
# modified to be compatible with streaming bodies,
|
83
|
+
# that automatically includes AppSignal's {EventHandler}.
|
84
|
+
#
|
85
|
+
# We recommend using this in combination with the
|
86
|
+
# {InstrumentationMiddleware}.
|
87
|
+
#
|
88
|
+
# This middleware will report the response status code as the
|
89
|
+
# `response_status` tag on the sample. It will also report the response
|
90
|
+
# status as the `response_status` metric.
|
91
|
+
#
|
92
|
+
# This middleware will ensure the AppSignal transaction is always completed
|
93
|
+
# for every request.
|
94
|
+
#
|
95
|
+
# @example Add EventMiddleware to a Rack app
|
96
|
+
# # Add this middleware as the first middleware of an app
|
97
|
+
# use Appsignal::Rack::EventMiddleware
|
98
|
+
#
|
99
|
+
# # Then add the InstrumentationMiddleware
|
100
|
+
# use Appsignal::Rack::InstrumentationMiddleware
|
101
|
+
#
|
102
|
+
# @see https://docs.appsignal.com/ruby/integrations/rack.html
|
103
|
+
# Rack integration documentation.
|
104
|
+
# @api public
|
105
|
+
class EventMiddleware < Events
|
106
|
+
def initialize(app)
|
107
|
+
super(app, [Appsignal::Rack::EventHandler.new])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -27,8 +27,8 @@ module Appsignal
|
|
27
27
|
# require "appsignal"
|
28
28
|
# # Configure and start AppSignal
|
29
29
|
#
|
30
|
-
# # Add the
|
31
|
-
# use
|
30
|
+
# # Add the EventMiddleware first
|
31
|
+
# use Appsignal::Rack::EventMiddleware
|
32
32
|
# # Add the instrumentation middleware second
|
33
33
|
# use Appsignal::Rack::InstrumentationMiddleware
|
34
34
|
#
|
data/lib/appsignal/rack.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Appsignal
|
4
|
-
#
|
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
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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}"
|