appsignal 4.9.1-java → 4.10.1-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: 1484edc2b0257539896eed6be1f56407fcaf454ab7c200c6bf798fea67d75c05
4
+ data.tar.gz: bc1cd76fce693264350284284af84e86bbc4956e84e36fc30af4b8d9b8ab5811
5
5
  SHA512:
6
- metadata.gz: 9011847dfd4f4547dfa03f8b007d7f201dc82c1eec598e2f6ddc15a05ef6a0fd75e6a3a2cee45eeac79a48f9c4d01c69056c3cbe594a15c6dfd875e37bcc519f
7
- data.tar.gz: 0cdf4856bbcd79f900569daf211e1e35e1e067ffaeb306ebc9af7eae4ec602299475f13e2cee474b8ed494853b3fa2e7eb33d90dbdad6f5a919f076a5a5a4938
6
+ metadata.gz: ce745eea54bcb98d7c2cf87acd060a1c6e162a25090bc0c653491fd926ef6ef26dd6e4f4a88516d4ac2560d943b7f720c88fade34276481d51c0a1cf277b7318
7
+ data.tar.gz: 35f4295a1a9685e44719a1a6723e3933a3777e495ffeb862884c4e1dc61979cb990ef093136ecea5a9040d05790c0925464eb0b9481fdfcd0f66da85747c3f7a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,108 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 4.10.1
4
+
5
+ _Published on 2026-08-20._
6
+
7
+ ### Fixed
8
+
9
+ - Use the CA certificate configured in `APPSIGNAL_CA_FILE_PATH` to download the
10
+ AppSignal agent when installing this gem.
11
+
12
+ The gem downloads the agent from our servers over HTTPS during installation. It
13
+ always used the CA certificate bundled with the gem to verify that connection,
14
+ whatever the value of the `ca_file_path` configuration option. This meant the
15
+ installation failed behind a proxy that intercepts TLS connections, such as
16
+ Zscaler, even when `APPSIGNAL_CA_FILE_PATH` pointed to a CA bundle that trusts
17
+ the proxy.
18
+
19
+ The bundled CA certificate is still used when `APPSIGNAL_CA_FILE_PATH` is not
20
+ set.
21
+
22
+ Thanks [@Cosmo](https://github.com/Cosmo) for your contribution!
23
+
24
+ (patch [5a126864](https://github.com/appsignal/appsignal-ruby/commit/5a126864314b0c4f244dab653c9548f48f7fd413), [0c49fc51](https://github.com/appsignal/appsignal-ruby/commit/0c49fc51a385d8e70a324792dc7dace3109b8424))
25
+
26
+ ## 4.10.0
27
+
28
+ _Published on 2026-08-10._
29
+
30
+ ### Changed
31
+
32
+ - Fix Excon requests being reported as taking almost no time. An Excon request was
33
+ recorded as two events, one for sending the request and one for reading the
34
+ response, and neither of them covered the wait for the remote service. So a slow
35
+ Excon request looked fast in the event timeline, however long it really took.
36
+
37
+ An Excon request is now recorded as a single `request.excon` event covering the
38
+ whole request, so the event lasts as long as the request did. A request that
39
+ Excon retried, or that was redirected, is also one event, covering every attempt
40
+ or every hop.
41
+
42
+ The `response.excon`, `retry.excon` and `error.excon` events no longer exist.
43
+ Excon does not tell an instrumentor which request those events belonged to, so
44
+ `response.excon` never had a title and `error.excon` was always titled `" ://"`.
45
+
46
+ AppSignal also no longer registers itself as Excon's instrumentor. Excon allows
47
+ only one instrumentor, so an application that set up its own was having it
48
+ replaced. Your own instrumentor now keeps working.
49
+
50
+ (minor [a931661f](https://github.com/appsignal/appsignal-ruby/commit/a931661f10c8a633384ba7ff71e01a03ae3cdbe5))
51
+ - Report which template was rendered for collection and layout render events.
52
+
53
+ Rendering a template or a partial is reported with the template's path, so you
54
+ can tell one from another. Rendering a collection or a layout was reported
55
+ without one. Every collection render in an application was recorded as the same
56
+ event, however many different partials it rendered, and so was every layout
57
+ render.
58
+
59
+ They now carry the template's path as well. A collection render reports the
60
+ partial it rendered for each item in the collection, and a layout render
61
+ reports the layout.
62
+
63
+ This means an application that renders several collections, or several layouts,
64
+ now sees one event per template where it used to see one event in total. That
65
+ is what makes it possible to tell which of them is the slow one.
66
+
67
+ (patch [79d8987a](https://github.com/appsignal/appsignal-ruby/commit/79d8987acbeb0d6d935f52f95b789f2f6a993bef))
68
+ - Name ROM query events after ROM, rather than after the database they ran
69
+ against.
70
+
71
+ A query made through ROM was reported as `query.postgres`, or `query.sqlite`,
72
+ or whatever else the application's database was. The part after the dot is what
73
+ AppSignal groups events by, so an application's queries were split into a group
74
+ per database engine, and the same application reported one group in production
75
+ and another one in its test suite. Those queries are now all reported as
76
+ `query.rom`.
77
+
78
+ Events that dry-monitor reports and AppSignal has no formatter for are now named
79
+ after their event id followed by `.dry`, so an event reported as `foo` becomes
80
+ `foo.dry`. They had no group at all before.
81
+
82
+ If you have a dashboard, trigger or saved filter that names one of these events,
83
+ point it at the new name.
84
+
85
+ (patch [effe80c7](https://github.com/appsignal/appsignal-ruby/commit/effe80c7c8341ceb2d4f99714d1130b292d2b16c))
86
+
87
+ ### Fixed
88
+
89
+ - Fix a rare hang when stopping AppSignal in an application that sends check-ins.
90
+ Stopping AppSignal waits for any check-in events that have not been transmitted
91
+ yet, and it could wait forever instead of finishing.
92
+
93
+ (patch [8956d4b8](https://github.com/appsignal/appsignal-ruby/commit/8956d4b88db9ae88a9bcd7e781ee62a79154c093))
94
+ - Fix event formatters that register or unregister themselves. Calling
95
+ `unregister` on the formatter itself, as in `MyFormatter.unregister("my.event")`,
96
+ did nothing at all. The formatter stayed registered, and no error was raised and
97
+ nothing was logged to say so. Calling `register` on the formatter itself stored
98
+ it where AppSignal never looked for it, so it was never used to format an event.
99
+
100
+ Registering and unregistering through `Appsignal::EventFormatter` itself, as in
101
+ `Appsignal::EventFormatter.unregister("my.event", MyFormatter)`, was not
102
+ affected and keeps working the same way.
103
+
104
+ (patch [ecfa4950](https://github.com/appsignal/appsignal-ruby/commit/ecfa495081c2a47c75017727e48bf17c7ae77351))
105
+
3
106
  ## 4.9.1
4
107
 
5
108
  _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:
data/ext/base.rb CHANGED
@@ -22,6 +22,13 @@ def ext_path(path)
22
22
  File.join(EXT_PATH, path)
23
23
  end
24
24
 
25
+ def ca_file_path
26
+ configured_path = ENV.fetch("APPSIGNAL_CA_FILE_PATH", nil)
27
+ return configured_path if configured_path && !configured_path.strip.empty?
28
+
29
+ CA_CERT_PATH
30
+ end
31
+
25
32
  def report
26
33
  @report ||=
27
34
  begin
@@ -132,7 +139,7 @@ def download_archive(type)
132
139
  proxy, _error = http_proxy
133
140
  args = [
134
141
  download_url,
135
- { :ssl_ca_cert => CA_CERT_PATH,
142
+ { :ssl_ca_cert => ca_file_path,
136
143
  :proxy => proxy }
137
144
  ]
138
145
  if URI.respond_to?(:open) # rubocop:disable Style/GuardClause
@@ -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.1"
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.1", 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.1
5
5
  platform: java
6
6
  authors:
7
7
  - Robert Beekman