appsignal 4.0.2-java → 4.0.4-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 +33 -0
- data/lib/appsignal/check_in/cron.rb +2 -34
- data/lib/appsignal/check_in/scheduler.rb +192 -0
- data/lib/appsignal/check_in.rb +18 -0
- data/lib/appsignal/cli/diagnose.rb +1 -1
- data/lib/appsignal/config.rb +3 -12
- data/lib/appsignal/hooks/at_exit.rb +2 -1
- data/lib/appsignal/integrations/railtie.rb +2 -7
- data/lib/appsignal/rack/body_wrapper.rb +15 -0
- data/lib/appsignal/transmitter.rb +30 -7
- data/lib/appsignal/utils/ndjson.rb +15 -0
- data/lib/appsignal/utils.rb +1 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +1 -0
- data/spec/lib/appsignal/check_in/cron_spec.rb +210 -0
- data/spec/lib/appsignal/check_in/scheduler_spec.rb +484 -0
- data/spec/lib/appsignal/config_spec.rb +0 -25
- data/spec/lib/appsignal/environment_spec.rb +23 -2
- data/spec/lib/appsignal/hooks/at_exit_spec.rb +11 -0
- data/spec/lib/appsignal/integrations/railtie_spec.rb +27 -6
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +29 -21
- data/spec/lib/appsignal/transmitter_spec.rb +48 -2
- data/spec/lib/appsignal_spec.rb +5 -0
- data/spec/support/helpers/take_at_most_helper.rb +21 -0
- metadata +7 -3
- data/spec/lib/appsignal/check_in_spec.rb +0 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d56061a26addb877656ebb516f8cdd174feb64651cee8816f67e9eaa1ac946f
|
4
|
+
data.tar.gz: c7d0582debd5c6d9ed29f2239a4baeed92ebdad46606c423e7537495b4dea3ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6710e1277f99b861d0c1981e300c21203c8ef91c3c49b23d39453b3970872e968f1eac140b9561684f1b569b7ab09406d352f06d37d852fa7c985ed858eb5d2
|
7
|
+
data.tar.gz: c0bf6a6ba6454fee105c6890db3154e03c0ad64c6b5ab325fb0186edf99066e923d7b6b4043ea4d77428155fbc58371ece1309517109437edc8cc265ffda554a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,38 @@
|
|
1
1
|
# AppSignal for Ruby gem Changelog
|
2
2
|
|
3
|
+
## 4.0.4
|
4
|
+
|
5
|
+
_Published on 2024-08-29._
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
|
9
|
+
- Send check-ins concurrently. When calling `Appsignal::CheckIn.cron`, instead of blocking the current thread while the check-in events are sent, schedule them to be sent in a separate thread.
|
10
|
+
|
11
|
+
When shutting down your application manually, call `Appsignal.stop` to block until all scheduled check-ins have been sent.
|
12
|
+
|
13
|
+
(patch [46d4ca74](https://github.com/appsignal/appsignal-ruby/commit/46d4ca74f4c188cc011653ed23969ad7ec770812))
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
- Make our Rack BodyWrapper behave like a Rack BodyProxy. If a method doesn't exist on our BodyWrapper class, but it does exist on the body, behave like the Rack BodyProxy and call the method on the wrapped body. (patch [e2376305](https://github.com/appsignal/appsignal-ruby/commit/e23763058a3fb980f1054e9c1eaf7e0f25f75666))
|
18
|
+
- Do not report `SignalException` errors from our `at_exit` error reporter. (patch [3ba3ce31](https://github.com/appsignal/appsignal-ruby/commit/3ba3ce31ee3f3e84665c9f2f18d488c689cff6c2))
|
19
|
+
|
20
|
+
## 4.0.3
|
21
|
+
|
22
|
+
_Published on 2024-08-26._
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
- Do not report Sidekiq `Sidekiq::JobRetry::Handled` and `Sidekiq::JobRetry::Skip` errors. These errors would be reported by our Rails error subscriber. These are an internal Sidekiq errors we do not need to report. (patch [e385ee2c](https://github.com/appsignal/appsignal-ruby/commit/e385ee2c4da13063e6f1a7a207286dda74113fc4))
|
27
|
+
|
28
|
+
### Removed
|
29
|
+
|
30
|
+
- Remove the `app_path` writer in the `Appsignal.configure` helper. This was deprecated in version 3.x. It is removed now in the next major version.
|
31
|
+
|
32
|
+
Use the `root_path` keyword argument in the `Appsignal.configure` helper (`Appsignal.configure(:root_path => "...")`) to change the AppSignal root path if necessary.
|
33
|
+
|
34
|
+
(patch [6335da6d](https://github.com/appsignal/appsignal-ruby/commit/6335da6d99a5ba7687fb5885eee27b9633d80474))
|
35
|
+
|
3
36
|
## 4.0.2
|
4
37
|
|
5
38
|
_Published on 2024-08-23._
|
@@ -3,15 +3,6 @@
|
|
3
3
|
module Appsignal
|
4
4
|
module CheckIn
|
5
5
|
class Cron
|
6
|
-
class << self
|
7
|
-
# @api private
|
8
|
-
def transmitter
|
9
|
-
@transmitter ||= Appsignal::Transmitter.new(
|
10
|
-
"#{Appsignal.config[:logging_endpoint]}/check_ins/json"
|
11
|
-
)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
6
|
# @api private
|
16
7
|
attr_reader :identifier, :digest
|
17
8
|
|
@@ -21,11 +12,11 @@ module Appsignal
|
|
21
12
|
end
|
22
13
|
|
23
14
|
def start
|
24
|
-
|
15
|
+
CheckIn.scheduler.schedule(event("start"))
|
25
16
|
end
|
26
17
|
|
27
18
|
def finish
|
28
|
-
|
19
|
+
CheckIn.scheduler.schedule(event("finish"))
|
29
20
|
end
|
30
21
|
|
31
22
|
private
|
@@ -39,29 +30,6 @@ module Appsignal
|
|
39
30
|
:check_in_type => "cron"
|
40
31
|
}
|
41
32
|
end
|
42
|
-
|
43
|
-
def transmit_event(kind)
|
44
|
-
unless Appsignal.active?
|
45
|
-
Appsignal.internal_logger.debug(
|
46
|
-
"AppSignal not active, not transmitting cron check-in event"
|
47
|
-
)
|
48
|
-
return
|
49
|
-
end
|
50
|
-
|
51
|
-
response = self.class.transmitter.transmit(event(kind))
|
52
|
-
|
53
|
-
if response.code.to_i >= 200 && response.code.to_i < 300
|
54
|
-
Appsignal.internal_logger.debug(
|
55
|
-
"Transmitted cron check-in `#{identifier}` (#{digest}) #{kind} event"
|
56
|
-
)
|
57
|
-
else
|
58
|
-
Appsignal.internal_logger.error(
|
59
|
-
"Failed to transmit cron check-in #{kind} event: status code was #{response.code}"
|
60
|
-
)
|
61
|
-
end
|
62
|
-
rescue => e
|
63
|
-
Appsignal.internal_logger.error("Failed to transmit cron check-in #{kind} event: #{e}")
|
64
|
-
end
|
65
33
|
end
|
66
34
|
end
|
67
35
|
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
module CheckIn
|
5
|
+
class Scheduler
|
6
|
+
INITIAL_DEBOUNCE_SECONDS = 0.1
|
7
|
+
BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS = 10
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# The mutex is used to synchronize access to the events array, the
|
11
|
+
# waker thread and the main thread, as well as queue writes
|
12
|
+
# (which depend on the events array) and closes (so they do not
|
13
|
+
# happen at the same time that an event is added to the scheduler)
|
14
|
+
@mutex = Mutex.new
|
15
|
+
# The transmitter thread will be started when an event is first added.
|
16
|
+
@thread = nil
|
17
|
+
@queue = Thread::Queue.new
|
18
|
+
# Scheduled events that have not been sent to the transmitter thread
|
19
|
+
# yet. A copy of this array is pushed to the queue by the waker thread
|
20
|
+
# after it has awaited the debounce period.
|
21
|
+
@events = []
|
22
|
+
# The waker thread is used to schedule debounces. It will be started
|
23
|
+
# when an event is first added.
|
24
|
+
@waker = nil
|
25
|
+
# For internal testing purposes.
|
26
|
+
@transmitted = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def schedule(event)
|
30
|
+
unless Appsignal.active?
|
31
|
+
Appsignal.internal_logger.debug(
|
32
|
+
"Cannot transmit #{describe([event])}: AppSignal is not active"
|
33
|
+
)
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
@mutex.synchronize do
|
38
|
+
if @queue.closed?
|
39
|
+
Appsignal.internal_logger.debug(
|
40
|
+
"Cannot transmit #{describe([event])}: AppSignal is stopped"
|
41
|
+
)
|
42
|
+
return
|
43
|
+
end
|
44
|
+
add_event(event)
|
45
|
+
# If we're not already waiting to be awakened from a scheduled
|
46
|
+
# debounce, schedule a short debounce, which will push the events
|
47
|
+
# to the queue and schedule a long debounce.
|
48
|
+
start_waker(INITIAL_DEBOUNCE_SECONDS) if @waker.nil?
|
49
|
+
|
50
|
+
Appsignal.internal_logger.debug(
|
51
|
+
"Scheduling #{describe([event])} to be transmitted"
|
52
|
+
)
|
53
|
+
|
54
|
+
# Make sure to start the thread after an event has been added.
|
55
|
+
@thread ||= Thread.new(&method(:run))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def stop
|
60
|
+
@mutex.synchronize do
|
61
|
+
# Flush all events before closing the queue.
|
62
|
+
push_events
|
63
|
+
rescue ClosedQueueError
|
64
|
+
# The queue is already closed (by a previous call to `#stop`)
|
65
|
+
# so it is not possible to push events to it anymore.
|
66
|
+
ensure
|
67
|
+
# Ensure calling `#stop` closes the queue and kills
|
68
|
+
# the waker thread, disallowing any further events from being
|
69
|
+
# scheduled with `#schedule`.
|
70
|
+
stop_waker
|
71
|
+
@queue.close
|
72
|
+
|
73
|
+
# Block until the thread has finished.
|
74
|
+
@thread&.join
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
# For internal testing purposes.
|
80
|
+
attr_reader :thread, :waker, :queue, :events, :transmitted
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def run
|
85
|
+
loop do
|
86
|
+
events = @queue.pop
|
87
|
+
break if events.nil?
|
88
|
+
|
89
|
+
transmit(events)
|
90
|
+
@transmitted += 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def transmit(events)
|
95
|
+
description = describe(events)
|
96
|
+
|
97
|
+
begin
|
98
|
+
response = CheckIn.transmitter.transmit(events, :format => :ndjson)
|
99
|
+
|
100
|
+
if (200...300).include?(response.code.to_i)
|
101
|
+
Appsignal.internal_logger.debug(
|
102
|
+
"Transmitted #{description}"
|
103
|
+
)
|
104
|
+
else
|
105
|
+
Appsignal.internal_logger.error(
|
106
|
+
"Failed to transmit #{description}: #{response.code} status code"
|
107
|
+
)
|
108
|
+
end
|
109
|
+
rescue => e
|
110
|
+
Appsignal.internal_logger.error("Failed to transmit #{description}: #{e.message}")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def describe(events)
|
115
|
+
if events.empty?
|
116
|
+
# This shouldn't happen.
|
117
|
+
"no check-in events"
|
118
|
+
elsif events.length > 1
|
119
|
+
"#{events.length} check-in events"
|
120
|
+
else
|
121
|
+
event = events.first
|
122
|
+
if event[:check_in_type] == "cron"
|
123
|
+
"cron check-in `#{event[:identifier] || "unknown"}` " \
|
124
|
+
"#{event[:kind] || "unknown"} event (digest #{event[:digest] || "unknown"})" \
|
125
|
+
else
|
126
|
+
"unknown check-in event"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Must be called from within a `@mutex.synchronize` block.
|
132
|
+
def add_event(event)
|
133
|
+
# Remove redundant events, keeping the newly added one, which
|
134
|
+
# should be the one with the most recent timestamp.
|
135
|
+
if event[:check_in_type] == "cron"
|
136
|
+
# Remove any existing cron check-in event with the same identifier,
|
137
|
+
# digest and kind as the one we're adding.
|
138
|
+
@events.reject! do |existing_event|
|
139
|
+
next unless existing_event[:identifier] == event[:identifier] &&
|
140
|
+
existing_event[:digest] == event[:digest] &&
|
141
|
+
existing_event[:kind] == event[:kind] &&
|
142
|
+
existing_event[:check_in_type] == "cron"
|
143
|
+
|
144
|
+
Appsignal.internal_logger.debug(
|
145
|
+
"Replacing previously scheduled #{describe([existing_event])}"
|
146
|
+
)
|
147
|
+
|
148
|
+
true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
@events << event
|
153
|
+
end
|
154
|
+
|
155
|
+
# Must be called from within a `@mutex.synchronize` block.
|
156
|
+
def start_waker(debounce)
|
157
|
+
stop_waker
|
158
|
+
|
159
|
+
@waker = Thread.new do
|
160
|
+
sleep(debounce)
|
161
|
+
|
162
|
+
@mutex.synchronize do
|
163
|
+
# Make sure this waker doesn't get killed, so it can push
|
164
|
+
# events and schedule a new waker.
|
165
|
+
@waker = nil
|
166
|
+
push_events
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Must be called from within a `@mutex.synchronize` block.
|
172
|
+
def stop_waker
|
173
|
+
@waker&.kill
|
174
|
+
@waker&.join
|
175
|
+
@waker = nil
|
176
|
+
end
|
177
|
+
|
178
|
+
# Must be called from within a `@mutex.synchronize` block.
|
179
|
+
def push_events
|
180
|
+
return if @events.empty?
|
181
|
+
|
182
|
+
# Push a copy of the events to the queue, and clear the events array.
|
183
|
+
# This ensures that `@events` always contains events that have not
|
184
|
+
# yet been pushed to the queue.
|
185
|
+
@queue.push(@events.dup)
|
186
|
+
@events.clear
|
187
|
+
|
188
|
+
start_waker(BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
data/lib/appsignal/check_in.rb
CHANGED
@@ -39,8 +39,26 @@ module Appsignal
|
|
39
39
|
cron.finish
|
40
40
|
output
|
41
41
|
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def transmitter
|
45
|
+
@transmitter ||= Transmitter.new(
|
46
|
+
"#{Appsignal.config[:logging_endpoint]}/check_ins/json"
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def scheduler
|
52
|
+
@scheduler ||= Scheduler.new
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def stop
|
57
|
+
scheduler&.stop
|
58
|
+
end
|
42
59
|
end
|
43
60
|
end
|
44
61
|
end
|
45
62
|
|
63
|
+
require "appsignal/check_in/scheduler"
|
46
64
|
require "appsignal/check_in/cron"
|
@@ -150,7 +150,7 @@ module Appsignal
|
|
150
150
|
ENV.fetch("APPSIGNAL_DIAGNOSE_ENDPOINT", DIAGNOSE_ENDPOINT),
|
151
151
|
Appsignal.config
|
152
152
|
)
|
153
|
-
response = transmitter.transmit(:diagnose => data)
|
153
|
+
response = transmitter.transmit({ :diagnose => data })
|
154
154
|
|
155
155
|
unless response.code == "200"
|
156
156
|
puts " Error: Something went wrong while submitting the report " \
|
data/lib/appsignal/config.rb
CHANGED
@@ -225,7 +225,7 @@ module Appsignal
|
|
225
225
|
# How to integrate AppSignal manually
|
226
226
|
def initialize(
|
227
227
|
root_path,
|
228
|
-
|
228
|
+
env,
|
229
229
|
logger = Appsignal.internal_logger
|
230
230
|
)
|
231
231
|
@root_path = root_path
|
@@ -234,8 +234,7 @@ module Appsignal
|
|
234
234
|
@logger = logger
|
235
235
|
@valid = false
|
236
236
|
|
237
|
-
@
|
238
|
-
@env = initial_env.to_s
|
237
|
+
@env = env.to_s
|
239
238
|
@config_hash = {}
|
240
239
|
@system_config = {}
|
241
240
|
@loaders_config = {}
|
@@ -270,7 +269,7 @@ module Appsignal
|
|
270
269
|
end
|
271
270
|
|
272
271
|
# Track origin of env
|
273
|
-
@initial_config[:env] = @
|
272
|
+
@initial_config[:env] = @env
|
274
273
|
|
275
274
|
# Load the config file if it exists
|
276
275
|
@file_config = load_from_disk || {}
|
@@ -560,14 +559,6 @@ module Appsignal
|
|
560
559
|
@config.root_path
|
561
560
|
end
|
562
561
|
|
563
|
-
def app_path=(_path)
|
564
|
-
Appsignal::Utils::StdoutAndLoggerMessage.warning \
|
565
|
-
"The `Appsignal.configure`'s `app_path=` writer is deprecated " \
|
566
|
-
"and can no longer be used to set the root path. " \
|
567
|
-
"Use the `Appsignal.configure`'s method `root_path` keyword argument " \
|
568
|
-
"to set the root path."
|
569
|
-
end
|
570
|
-
|
571
562
|
def env
|
572
563
|
@config.env
|
573
564
|
end
|
@@ -102,14 +102,9 @@ module Appsignal
|
|
102
102
|
|
103
103
|
private
|
104
104
|
|
105
|
-
IGNORED_ERRORS = [
|
106
|
-
# We don't need to alert Sidekiq job skip errors.
|
107
|
-
# This is an internal Sidekiq error.
|
108
|
-
"Sidekiq::JobRetry::Skip"
|
109
|
-
].freeze
|
110
|
-
|
111
105
|
def ignored_error?(error)
|
112
|
-
|
106
|
+
# We don't need to alert about Sidekiq job internal errors.
|
107
|
+
defined?(Sidekiq::JobRetry::Handled) && error.is_a?(Sidekiq::JobRetry::Handled)
|
113
108
|
end
|
114
109
|
|
115
110
|
def context_for(context)
|
@@ -57,6 +57,21 @@ module Appsignal
|
|
57
57
|
@transaction.set_error(error)
|
58
58
|
raise error
|
59
59
|
end
|
60
|
+
|
61
|
+
# Return whether the wrapped body responds to the method if this class does not.
|
62
|
+
# Based on:
|
63
|
+
# https://github.com/rack/rack/blob/0ed580bbe3858ffe5d530adf1bdad9ef9c03407c/lib/rack/body_proxy.rb#L16-L24
|
64
|
+
def respond_to_missing?(method_name, include_all = false)
|
65
|
+
super || @body.respond_to?(method_name, include_all)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Delegate missing methods to the wrapped body.
|
69
|
+
# Based on:
|
70
|
+
# https://github.com/rack/rack/blob/0ed580bbe3858ffe5d530adf1bdad9ef9c03407c/lib/rack/body_proxy.rb#L44-L61
|
71
|
+
def method_missing(method_name, *args, &block)
|
72
|
+
@body.__send__(method_name, *args, &block)
|
73
|
+
end
|
74
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
60
75
|
end
|
61
76
|
|
62
77
|
# The standard Rack body wrapper which exposes "each" for iterating
|
@@ -9,7 +9,8 @@ require "json"
|
|
9
9
|
module Appsignal
|
10
10
|
# @api private
|
11
11
|
class Transmitter
|
12
|
-
|
12
|
+
JSON_CONTENT_TYPE = "application/json; charset=UTF-8"
|
13
|
+
NDJSON_CONTENT_TYPE = "application/x-ndjson; charset=UTF-8"
|
13
14
|
|
14
15
|
HTTP_ERRORS = [
|
15
16
|
EOFError,
|
@@ -53,17 +54,39 @@ module Appsignal
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
56
|
-
def transmit(payload)
|
57
|
-
|
58
|
-
http_client.request(http_post(payload))
|
57
|
+
def transmit(payload, format: :json)
|
58
|
+
Appsignal.internal_logger.debug "Transmitting payload to #{uri}"
|
59
|
+
http_client.request(http_post(payload, :format => format))
|
59
60
|
end
|
60
61
|
|
61
62
|
private
|
62
63
|
|
63
|
-
def http_post(payload)
|
64
|
+
def http_post(payload, format: :json)
|
64
65
|
Net::HTTP::Post.new(uri.request_uri).tap do |request|
|
65
|
-
request["Content-Type"] =
|
66
|
-
request.body =
|
66
|
+
request["Content-Type"] = content_type_for(format)
|
67
|
+
request.body = generate_body_for(format, payload)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def content_type_for(format)
|
72
|
+
case format
|
73
|
+
when :json
|
74
|
+
JSON_CONTENT_TYPE
|
75
|
+
when :ndjson
|
76
|
+
NDJSON_CONTENT_TYPE
|
77
|
+
else
|
78
|
+
raise ArgumentError, "Unknown Content-Type header for format: #{format}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def generate_body_for(format, payload)
|
83
|
+
case format
|
84
|
+
when :json
|
85
|
+
Appsignal::Utils::JSON.generate(payload)
|
86
|
+
when :ndjson
|
87
|
+
Appsignal::Utils::NDJSON.generate(payload)
|
88
|
+
else
|
89
|
+
raise ArgumentError, "Unknown body generator for format: #{format}"
|
67
90
|
end
|
68
91
|
end
|
69
92
|
|
data/lib/appsignal/utils.rb
CHANGED
data/lib/appsignal/version.rb
CHANGED