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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ab7d841379d8fb72ce94d809133987920e063dbe72ec65b026360121685c83f
4
- data.tar.gz: 253ac53b17b30ce57a5c25677b9c2dad9c33e6a20cd937311897a82a6301b33e
3
+ metadata.gz: f6d6acf8b543263a2008bfc4ea0f20a57194d01aa2acdc5886292e8ead23e112
4
+ data.tar.gz: b1f7a59ef1b33459306bfb60a0681e581e1c1dd9be78a315d08d419c310e3296
5
5
  SHA512:
6
- metadata.gz: 9011847dfd4f4547dfa03f8b007d7f201dc82c1eec598e2f6ddc15a05ef6a0fd75e6a3a2cee45eeac79a48f9c4d01c69056c3cbe594a15c6dfd875e37bcc519f
7
- data.tar.gz: 0cdf4856bbcd79f900569daf211e1e35e1e067ffaeb306ebc9af7eae4ec602299475f13e2cee474b8ed494853b3fa2e7eb33d90dbdad6f5a919f076a5a5a4938
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
@@ -151,6 +151,7 @@ matrix:
151
151
  - "3.2.5"
152
152
  - "3.1.6"
153
153
  - "3.0.7"
154
+ - gem: "excon"
154
155
  - gem: "faraday-1"
155
156
  - gem: "faraday-2"
156
157
  only:
@@ -60,21 +60,35 @@ module Appsignal
60
60
  end
61
61
 
62
62
  def stop
63
- @mutex.synchronize do
64
- # Flush all events before closing the queue.
65
- push_events
66
- rescue ClosedQueueError
67
- # The queue is already closed (by a previous call to `#stop`)
68
- # so it is not possible to push events to it anymore.
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
- # Ensure calling `#stop` closes the queue and kills
71
- # the waker thread, disallowing any further events from being
72
- # scheduled with `#schedule`.
73
- stop_waker
74
- @queue.close
75
-
76
- # Block until the thread has finished.
77
- @thread&.join
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
- stop_waker
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
- def stop_waker
154
- @waker&.kill
155
- @waker&.join
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,7 +4,7 @@ module Appsignal
4
4
  class EventFormatter
5
5
  # @!visibility private
6
6
  module ActiveRecord
7
- class InstantiationFormatter
7
+ class InstantiationFormatter < Appsignal::EventFormatter
8
8
  def format(payload)
9
9
  [payload[:class_name], nil]
10
10
  end
@@ -4,7 +4,7 @@ module Appsignal
4
4
  class EventFormatter
5
5
  # @!visibility private
6
6
  module ActiveRecord
7
- class SqlFormatter
7
+ class SqlFormatter < Appsignal::EventFormatter
8
8
  def format(payload)
9
9
  [payload[:name], payload[:sql], SQL_BODY_FORMAT]
10
10
  end
@@ -4,7 +4,7 @@ module Appsignal
4
4
  class EventFormatter
5
5
  # @!visibility private
6
6
  module ElasticSearch
7
- class SearchFormatter
7
+ class SearchFormatter < Appsignal::EventFormatter
8
8
  def format(payload)
9
9
  [
10
10
  "#{payload[:name]}: #{payload[:klass]}",
@@ -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.#{payload[:name]}", payload[:query], SQL_BODY_FORMAT]
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
@@ -4,7 +4,7 @@ module Appsignal
4
4
  class EventFormatter
5
5
  # @!visibility private
6
6
  module ViewComponent
7
- class RenderFormatter
7
+ class RenderFormatter < Appsignal::EventFormatter
8
8
  BLANK = ""
9
9
 
10
10
  def format(payload)
@@ -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
- @formatters ||= {}
28
+ REGISTRY[:formatters]
18
29
  end
19
30
 
20
31
  # @!visibility private
21
32
  def formatter_classes
22
- @formatter_classes ||= {}
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
- ::Excon.defaults[:instrumentor] = Appsignal::Integrations::ExconIntegration
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
- title, body, body_format = Appsignal::EventFormatter.format("#{event_id}.dry", payload)
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
- title || event_id.to_s,
16
- title,
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
- def self.instrument(name, data, &block)
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. Excon calls the
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 block_given? ? yield : nil
24
+ return super
14
25
  end
15
26
 
16
- namespace, *event = name.split(".")
17
- rails_name = [event, namespace].flatten.join(".")
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 rails_name == "response.excon"
21
- data[:host]
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
- "#{data[:method].to_s.upcase} #{data[:scheme]}://#{data[:host]}"
51
+ super
24
52
  end
25
- Appsignal.instrument(rails_name, title, &block)
53
+ end
26
54
  end
27
55
  end
28
56
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Appsignal
4
4
  # @return [String]
5
- VERSION = "4.9.1"
5
+ VERSION = "4.10.0"
6
6
  end
data/sig/appsignal.rbi CHANGED
@@ -8,7 +8,7 @@
8
8
  module Appsignal
9
9
  extend Appsignal::Helpers::Metrics
10
10
  extend Appsignal::Helpers::Instrumentation
11
- VERSION = T.let("4.9.1", T.untyped)
11
+ VERSION = T.let("4.10.0", T.untyped)
12
12
 
13
13
  class << self
14
14
  # The loaded AppSignal configuration.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.9.1
4
+ version: 4.10.0
5
5
  platform: java
6
6
  authors:
7
7
  - Robert Beekman