appsignal 4.9.1-java → 4.10.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 +80 -0
- data/build_matrix.yml +1 -0
- data/lib/appsignal/check_in/scheduler.rb +46 -20
- data/lib/appsignal/event_formatter/action_view/render_formatter.rb +12 -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/rom/sql_formatter.rb +11 -2
- 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 +13 -2
- data/lib/appsignal/hooks/excon.rb +8 -1
- data/lib/appsignal/integrations/dry_monitor.rb +8 -3
- data/lib/appsignal/integrations/excon.rb +39 -11
- data/lib/appsignal/version.rb +1 -1
- data/sig/appsignal.rbi +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6d6acf8b543263a2008bfc4ea0f20a57194d01aa2acdc5886292e8ead23e112
|
|
4
|
+
data.tar.gz: b1f7a59ef1b33459306bfb60a0681e581e1c1dd9be78a315d08d419c310e3296
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7181d57c290de3067026ec31912c5da273b2dc7597453eda533f61b82f2654cbd467e81bb572ab93cd0fbdd38abdb582ba17a67de3a71185c1474724eba5254d
|
|
7
|
+
data.tar.gz: f4c27203476610873135cf6db61ae788c4f5bbab59b56f7cddd0a2bb2f657dfd19a3224ffddd240cde62b58dad51cfdfdaa078b1083ec81f33e75178e3cc445e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# AppSignal for Ruby gem Changelog
|
|
2
2
|
|
|
3
|
+
## 4.10.0
|
|
4
|
+
|
|
5
|
+
_Published on 2026-08-10._
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Fix Excon requests being reported as taking almost no time. An Excon request was
|
|
10
|
+
recorded as two events, one for sending the request and one for reading the
|
|
11
|
+
response, and neither of them covered the wait for the remote service. So a slow
|
|
12
|
+
Excon request looked fast in the event timeline, however long it really took.
|
|
13
|
+
|
|
14
|
+
An Excon request is now recorded as a single `request.excon` event covering the
|
|
15
|
+
whole request, so the event lasts as long as the request did. A request that
|
|
16
|
+
Excon retried, or that was redirected, is also one event, covering every attempt
|
|
17
|
+
or every hop.
|
|
18
|
+
|
|
19
|
+
The `response.excon`, `retry.excon` and `error.excon` events no longer exist.
|
|
20
|
+
Excon does not tell an instrumentor which request those events belonged to, so
|
|
21
|
+
`response.excon` never had a title and `error.excon` was always titled `" ://"`.
|
|
22
|
+
|
|
23
|
+
AppSignal also no longer registers itself as Excon's instrumentor. Excon allows
|
|
24
|
+
only one instrumentor, so an application that set up its own was having it
|
|
25
|
+
replaced. Your own instrumentor now keeps working.
|
|
26
|
+
|
|
27
|
+
(minor [a931661f](https://github.com/appsignal/appsignal-ruby/commit/a931661f10c8a633384ba7ff71e01a03ae3cdbe5))
|
|
28
|
+
- Report which template was rendered for collection and layout render events.
|
|
29
|
+
|
|
30
|
+
Rendering a template or a partial is reported with the template's path, so you
|
|
31
|
+
can tell one from another. Rendering a collection or a layout was reported
|
|
32
|
+
without one. Every collection render in an application was recorded as the same
|
|
33
|
+
event, however many different partials it rendered, and so was every layout
|
|
34
|
+
render.
|
|
35
|
+
|
|
36
|
+
They now carry the template's path as well. A collection render reports the
|
|
37
|
+
partial it rendered for each item in the collection, and a layout render
|
|
38
|
+
reports the layout.
|
|
39
|
+
|
|
40
|
+
This means an application that renders several collections, or several layouts,
|
|
41
|
+
now sees one event per template where it used to see one event in total. That
|
|
42
|
+
is what makes it possible to tell which of them is the slow one.
|
|
43
|
+
|
|
44
|
+
(patch [79d8987a](https://github.com/appsignal/appsignal-ruby/commit/79d8987acbeb0d6d935f52f95b789f2f6a993bef))
|
|
45
|
+
- Name ROM query events after ROM, rather than after the database they ran
|
|
46
|
+
against.
|
|
47
|
+
|
|
48
|
+
A query made through ROM was reported as `query.postgres`, or `query.sqlite`,
|
|
49
|
+
or whatever else the application's database was. The part after the dot is what
|
|
50
|
+
AppSignal groups events by, so an application's queries were split into a group
|
|
51
|
+
per database engine, and the same application reported one group in production
|
|
52
|
+
and another one in its test suite. Those queries are now all reported as
|
|
53
|
+
`query.rom`.
|
|
54
|
+
|
|
55
|
+
Events that dry-monitor reports and AppSignal has no formatter for are now named
|
|
56
|
+
after their event id followed by `.dry`, so an event reported as `foo` becomes
|
|
57
|
+
`foo.dry`. They had no group at all before.
|
|
58
|
+
|
|
59
|
+
If you have a dashboard, trigger or saved filter that names one of these events,
|
|
60
|
+
point it at the new name.
|
|
61
|
+
|
|
62
|
+
(patch [effe80c7](https://github.com/appsignal/appsignal-ruby/commit/effe80c7c8341ceb2d4f99714d1130b292d2b16c))
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- Fix a rare hang when stopping AppSignal in an application that sends check-ins.
|
|
67
|
+
Stopping AppSignal waits for any check-in events that have not been transmitted
|
|
68
|
+
yet, and it could wait forever instead of finishing.
|
|
69
|
+
|
|
70
|
+
(patch [8956d4b8](https://github.com/appsignal/appsignal-ruby/commit/8956d4b88db9ae88a9bcd7e781ee62a79154c093))
|
|
71
|
+
- Fix event formatters that register or unregister themselves. Calling
|
|
72
|
+
`unregister` on the formatter itself, as in `MyFormatter.unregister("my.event")`,
|
|
73
|
+
did nothing at all. The formatter stayed registered, and no error was raised and
|
|
74
|
+
nothing was logged to say so. Calling `register` on the formatter itself stored
|
|
75
|
+
it where AppSignal never looked for it, so it was never used to format an event.
|
|
76
|
+
|
|
77
|
+
Registering and unregistering through `Appsignal::EventFormatter` itself, as in
|
|
78
|
+
`Appsignal::EventFormatter.unregister("my.event", MyFormatter)`, was not
|
|
79
|
+
affected and keeps working the same way.
|
|
80
|
+
|
|
81
|
+
(patch [ecfa4950](https://github.com/appsignal/appsignal-ruby/commit/ecfa495081c2a47c75017727e48bf17c7ae77351))
|
|
82
|
+
|
|
3
83
|
## 4.9.1
|
|
4
84
|
|
|
5
85
|
_Published on 2026-07-27._
|
data/build_matrix.yml
CHANGED
|
@@ -60,21 +60,35 @@ module Appsignal
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def stop
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
waker = nil
|
|
64
|
+
thread = nil
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
# Flush all events before closing the queue. Do not schedule another
|
|
69
|
+
# debounce, since no more events will be transmitted after this one.
|
|
70
|
+
push_events(:reschedule => false)
|
|
71
|
+
rescue ClosedQueueError
|
|
72
|
+
# The queue is already closed (by a previous call to `#stop`)
|
|
73
|
+
# so it is not possible to push events to it anymore.
|
|
74
|
+
ensure
|
|
75
|
+
# Ensure calling `#stop` closes the queue and kills
|
|
76
|
+
# the waker thread, disallowing any further events from being
|
|
77
|
+
# scheduled with `#schedule`.
|
|
78
|
+
waker = kill_waker
|
|
79
|
+
@queue.close
|
|
80
|
+
thread = @thread
|
|
81
|
+
end
|
|
69
82
|
ensure
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
#
|
|
77
|
-
|
|
83
|
+
# Block until both threads have finished, even when stopping raised,
|
|
84
|
+
# so that events that were already pushed are still transmitted.
|
|
85
|
+
#
|
|
86
|
+
# Wait for them after the mutex has been released, because the waker
|
|
87
|
+
# thread must acquire the mutex in order to finish. Waiting for it
|
|
88
|
+
# while holding the mutex would deadlock if killing it did not take
|
|
89
|
+
# effect before it awoke from its debounce.
|
|
90
|
+
waker&.join
|
|
91
|
+
thread&.join
|
|
78
92
|
end
|
|
79
93
|
end
|
|
80
94
|
|
|
@@ -135,12 +149,16 @@ module Appsignal
|
|
|
135
149
|
|
|
136
150
|
# Must be called from within a `@mutex.synchronize` block.
|
|
137
151
|
def start_waker(debounce)
|
|
138
|
-
|
|
152
|
+
kill_waker
|
|
139
153
|
|
|
140
154
|
@waker = Thread.new do
|
|
141
155
|
sleep(debounce)
|
|
142
156
|
|
|
143
157
|
@mutex.synchronize do
|
|
158
|
+
# Do nothing if this waker was replaced while it slept, which can
|
|
159
|
+
# happen when killing it did not take effect.
|
|
160
|
+
next unless @waker == Thread.current
|
|
161
|
+
|
|
144
162
|
# Make sure this waker doesn't get killed, so it can push
|
|
145
163
|
# events and schedule a new waker.
|
|
146
164
|
@waker = nil
|
|
@@ -150,14 +168,22 @@ module Appsignal
|
|
|
150
168
|
end
|
|
151
169
|
|
|
152
170
|
# Must be called from within a `@mutex.synchronize` block.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
171
|
+
#
|
|
172
|
+
# Returns the waker thread that was killed, if there was one, so that the
|
|
173
|
+
# caller can wait for it to finish after releasing the mutex.
|
|
174
|
+
def kill_waker
|
|
175
|
+
waker = @waker
|
|
156
176
|
@waker = nil
|
|
177
|
+
waker&.kill
|
|
178
|
+
waker
|
|
157
179
|
end
|
|
158
180
|
|
|
159
181
|
# Must be called from within a `@mutex.synchronize` block.
|
|
160
|
-
def push_events
|
|
182
|
+
def push_events(reschedule: true)
|
|
183
|
+
# Do nothing when the queue is closed. This can happen when killing a
|
|
184
|
+
# waker thread did not take effect, and it awoke after the scheduler
|
|
185
|
+
# had been stopped.
|
|
186
|
+
return if @queue.closed?
|
|
161
187
|
return if @events.empty?
|
|
162
188
|
|
|
163
189
|
# Push a copy of the events to the queue, and clear the events array.
|
|
@@ -168,7 +194,7 @@ module Appsignal
|
|
|
168
194
|
@queue.push(events)
|
|
169
195
|
@events.clear
|
|
170
196
|
|
|
171
|
-
start_waker(BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS)
|
|
197
|
+
start_waker(BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS) if reschedule
|
|
172
198
|
end
|
|
173
199
|
end
|
|
174
200
|
end
|
|
@@ -4,7 +4,7 @@ module Appsignal
|
|
|
4
4
|
class EventFormatter
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module ActionView
|
|
7
|
-
class RenderFormatter
|
|
7
|
+
class RenderFormatter < Appsignal::EventFormatter
|
|
8
8
|
BLANK = ""
|
|
9
9
|
|
|
10
10
|
def format(payload)
|
|
@@ -30,4 +30,15 @@ if defined?(Rails)
|
|
|
30
30
|
"render_template.action_view",
|
|
31
31
|
Appsignal::EventFormatter::ActionView::RenderFormatter
|
|
32
32
|
)
|
|
33
|
+
# Action View reports the template's path for these two as well, so they are
|
|
34
|
+
# titled the same way. A collection reports the partial it rendered for each
|
|
35
|
+
# item, and a layout reports itself.
|
|
36
|
+
Appsignal::EventFormatter.register(
|
|
37
|
+
"render_collection.action_view",
|
|
38
|
+
Appsignal::EventFormatter::ActionView::RenderFormatter
|
|
39
|
+
)
|
|
40
|
+
Appsignal::EventFormatter.register(
|
|
41
|
+
"render_layout.action_view",
|
|
42
|
+
Appsignal::EventFormatter::ActionView::RenderFormatter
|
|
43
|
+
)
|
|
33
44
|
end
|
|
@@ -4,9 +4,18 @@ module Appsignal
|
|
|
4
4
|
class EventFormatter
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module Rom
|
|
7
|
-
class SqlFormatter
|
|
7
|
+
class SqlFormatter < Appsignal::EventFormatter
|
|
8
|
+
# dry-monitor reports an event under an id rather than a name, so the
|
|
9
|
+
# first value here names the event. Naming it after ROM keeps every ROM
|
|
10
|
+
# query in one group.
|
|
11
|
+
#
|
|
12
|
+
# The payload also says which database ROM is talking to, as Sequel's
|
|
13
|
+
# database type. That is deliberately not part of the name. It would
|
|
14
|
+
# put an application's queries in a group per database engine, so the
|
|
15
|
+
# same application would report one group in production and another in
|
|
16
|
+
# its tests.
|
|
8
17
|
def format(payload)
|
|
9
|
-
["query
|
|
18
|
+
["query.rom", payload[:query], SQL_BODY_FORMAT]
|
|
10
19
|
end
|
|
11
20
|
end
|
|
12
21
|
end
|
|
@@ -9,7 +9,7 @@ module Appsignal
|
|
|
9
9
|
# that conflict with our own sequel instrumentor. Without this event
|
|
10
10
|
# formatter the sequel-rails events are recorded without the SQL query
|
|
11
11
|
# that's being executed.
|
|
12
|
-
class SqlFormatter
|
|
12
|
+
class SqlFormatter < Appsignal::EventFormatter
|
|
13
13
|
def format(payload)
|
|
14
14
|
[payload[:name].to_s, payload[:sql], SQL_BODY_FORMAT]
|
|
15
15
|
end
|
|
@@ -11,15 +11,26 @@ module Appsignal
|
|
|
11
11
|
# So only keep global configuration as state and pass the payload around as an
|
|
12
12
|
# argument if you need to use helper methods.
|
|
13
13
|
class EventFormatter
|
|
14
|
+
# There is one registry of event formatters, shared by this class and by
|
|
15
|
+
# every formatter that inherits from it. It is kept in a constant because a
|
|
16
|
+
# class instance variable is not shared with subclasses. Without this, a
|
|
17
|
+
# formatter that registers or unregisters itself would read and write a
|
|
18
|
+
# registry of its own that nothing else looks at.
|
|
19
|
+
REGISTRY = {
|
|
20
|
+
:formatters => {},
|
|
21
|
+
:formatter_classes => {}
|
|
22
|
+
}.freeze
|
|
23
|
+
private_constant :REGISTRY
|
|
24
|
+
|
|
14
25
|
class << self
|
|
15
26
|
# @!visibility private
|
|
16
27
|
def formatters
|
|
17
|
-
|
|
28
|
+
REGISTRY[:formatters]
|
|
18
29
|
end
|
|
19
30
|
|
|
20
31
|
# @!visibility private
|
|
21
32
|
def formatter_classes
|
|
22
|
-
|
|
33
|
+
REGISTRY[:formatter_classes]
|
|
23
34
|
end
|
|
24
35
|
|
|
25
36
|
# Registers an event formatter for a specific event name.
|
|
@@ -12,7 +12,14 @@ module Appsignal
|
|
|
12
12
|
|
|
13
13
|
def install
|
|
14
14
|
require "appsignal/integrations/excon"
|
|
15
|
-
|
|
15
|
+
# Instrument the request at the connection, rather than by registering
|
|
16
|
+
# AppSignal as Excon's instrumentor. An instrumentor is told about a
|
|
17
|
+
# request in pieces, none of which covers the wait for the response, and
|
|
18
|
+
# there is only room for one of them, so registering ours would replace
|
|
19
|
+
# any the application set up itself.
|
|
20
|
+
::Excon::Connection.prepend Appsignal::Integrations::ExconIntegration
|
|
21
|
+
|
|
22
|
+
Appsignal::Environment.report_enabled("excon")
|
|
16
23
|
end
|
|
17
24
|
end
|
|
18
25
|
end
|
|
@@ -9,11 +9,16 @@ module Appsignal
|
|
|
9
9
|
|
|
10
10
|
super
|
|
11
11
|
ensure
|
|
12
|
-
|
|
12
|
+
name = "#{event_id}.dry"
|
|
13
|
+
event_name, body, body_format = Appsignal::EventFormatter.format(name, payload)
|
|
13
14
|
|
|
15
|
+
# dry-monitor reports an event under an id, such as `sql`, rather than
|
|
16
|
+
# a name. A formatter names the event it knows about, and an event
|
|
17
|
+
# without one is named after its id in the dry-monitor group. Either
|
|
18
|
+
# way the name has a group, which is what an event is listed under.
|
|
14
19
|
Appsignal::Transaction.current.finish_event(
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
event_name || name,
|
|
21
|
+
nil,
|
|
17
22
|
body,
|
|
18
23
|
body_format
|
|
19
24
|
)
|
|
@@ -4,25 +4,53 @@ module Appsignal
|
|
|
4
4
|
module Integrations
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module ExconIntegration
|
|
7
|
-
|
|
7
|
+
# The title of the event, built the way the Net::HTTP integration builds
|
|
8
|
+
# its own: the request method and where the request went, without the
|
|
9
|
+
# path, so paths stay out of event titles.
|
|
10
|
+
#
|
|
11
|
+
# Excon splits a request's data between the connection it is made on and
|
|
12
|
+
# the call that makes it, so both are read to build this. Excon defaults
|
|
13
|
+
# the method to GET itself, so the same default is applied here.
|
|
14
|
+
def self.title_for(datum)
|
|
15
|
+
method = (datum[:method] || :get).to_s.upcase
|
|
16
|
+
"#{method} #{datum[:scheme]}://#{datum[:host]}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def request(params = {})
|
|
8
20
|
# Skip when an outer HTTP client integration (Faraday) already records
|
|
9
|
-
# this request, so it isn't instrumented twice.
|
|
10
|
-
# instrumentor for block-less notifications too, hence the `block_given?`.
|
|
21
|
+
# this request, so it isn't instrumented twice.
|
|
11
22
|
if Appsignal::Transaction.current? &&
|
|
12
23
|
Appsignal::Transaction.current.http_client_events_suppressed?
|
|
13
|
-
return
|
|
24
|
+
return super
|
|
14
25
|
end
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
# This method is the whole of a request. Excon sends the request here,
|
|
28
|
+
# waits for the response, reads it, and only then returns.
|
|
29
|
+
#
|
|
30
|
+
# Excon can also report a request through an instrumentor, but an
|
|
31
|
+
# instrumentor cannot measure a request. Excon runs its middleware once
|
|
32
|
+
# to send the request and again to read the response, and the reading
|
|
33
|
+
# happens in a middleware that sits outside the one that calls the
|
|
34
|
+
# instrumentor. So the wait for the remote service, which is the part of
|
|
35
|
+
# a request worth measuring, falls outside everything an instrumentor is
|
|
36
|
+
# told about.
|
|
37
|
+
#
|
|
38
|
+
# A pipelined request is the exception to this method being the whole of
|
|
39
|
+
# a request. It returns before the response is read, so its event covers
|
|
40
|
+
# only the sending.
|
|
41
|
+
title = ExconIntegration.title_for(data.merge(params))
|
|
18
42
|
|
|
19
|
-
title
|
|
20
|
-
if
|
|
21
|
-
|
|
43
|
+
Appsignal.instrument("request.excon", title) do
|
|
44
|
+
if Appsignal::Transaction.current?
|
|
45
|
+
# Excon retries a request, and follows a redirect, by calling this
|
|
46
|
+
# method again from inside the request it is retrying or following.
|
|
47
|
+
# Suppressing those means they count towards this event rather than
|
|
48
|
+
# becoming events of their own, so one request stays one event.
|
|
49
|
+
Appsignal::Transaction.current.suppress_http_client_events { super }
|
|
22
50
|
else
|
|
23
|
-
|
|
51
|
+
super
|
|
24
52
|
end
|
|
25
|
-
|
|
53
|
+
end
|
|
26
54
|
end
|
|
27
55
|
end
|
|
28
56
|
end
|
data/lib/appsignal/version.rb
CHANGED
data/sig/appsignal.rbi
CHANGED