appsignal 5.0.2-java → 5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62c71c72cf65e314eed6a65860763d7e5875664e630852702ff77af7dd8cf901
4
- data.tar.gz: d28d7b78d58597f8e4ce6f37111110405acd0cdcdc38efc290b6131c57501c86
3
+ metadata.gz: 8cbb7e4a2eea9564a48fa144018d1d6b2a3a0071145dbb83667e1f35dd79ced2
4
+ data.tar.gz: 94ca97dc758b43867456f9ca351085ba0f43945d55c94ef777d83af89a77c2d2
5
5
  SHA512:
6
- metadata.gz: 54f8a659c2178f588165b2c24cdc8d7906d4ce290b4904eb9fb1c8240eda1c314bfa062faa1f718a1996b2b2dbe3c75cdf7d7893f6fce64344c763700cf7451a
7
- data.tar.gz: 1dcb25bc71bed601aac8cf9a499182227f8f8b36c2550a203780b57a80f8758a061fb6a7c7b9427ec39b46828abfb6a6c47225aa602f89516d0d6142aa36878c
6
+ metadata.gz: a78c3d8478e1be4f5ba71a555042be872889ceec267b7fa623068059cdf928434141f19ae4635ffd7b0fe8d14ffc075266d1b95db36655dbaebde41bd381c8cb
7
+ data.tar.gz: c848362c51a845da9b0f5be46180d857b14a58152765664d24abdf89244fb4c7efe9569a18dda2a3549e04cbb21465468eef5d6fd12164c8c5e73954f777921f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 5.0.4
4
+
5
+ _Published on 2026-10-09._
6
+
7
+ ### Fixed
8
+
9
+ - Fix an issue where transactions would be reused across requests when an error happened before the transaction could be closed, which would lead to errors being returned by the app itself. (patch [65ff0be0](https://github.com/appsignal/appsignal-ruby/commit/65ff0be0c3d4dea2eb9ed5bdb81004e899e6edf4))
10
+
11
+ ## 5.0.3
12
+
13
+ _Published on 2026-10-08._
14
+
15
+ ### Fixed
16
+
17
+ - In collector mode, report counter metrics correctly from processes forked after AppSignal starts, such as Puma workers when using `preload_app!`. Before this change, applications running several forked processes would lose data points or report incorrect values for them. (patch [4e5a3e78](https://github.com/appsignal/appsignal-ruby/commit/4e5a3e781b4ce2b2620b172a15198a2a59e0ebee))
18
+
3
19
  ## 5.0.2
4
20
 
5
21
  _Published on 2026-10-06._
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Appsignal
6
+ module OpenTelemetry
7
+ # @!visibility private
8
+ #
9
+ # The resource attributes that identify the current process:
10
+ # `service.instance.id` and `process.pid`. The OpenTelemetry providers are
11
+ # built once, so a process forked after that inherits its parent's values
12
+ # until {.attach_fork_hook} refreshes them in the child.
13
+ module ProcessResource
14
+ SERVICE_INSTANCE_ID = "service.instance.id"
15
+ PROCESS_PID = "process.pid"
16
+ REFRESH_FAILED = "Could not refresh the OpenTelemetry resource after fork"
17
+
18
+ class << self
19
+ # A random UUIDv4, generated again when the process ID changes.
20
+ def service_instance_id
21
+ pid = Process.pid
22
+ unless @service_instance_id_pid == pid
23
+ @service_instance_id = SecureRandom.uuid
24
+ @service_instance_id_pid = pid
25
+ end
26
+ @service_instance_id
27
+ end
28
+
29
+ def resource
30
+ ::OpenTelemetry::SDK::Resources::Resource.create(
31
+ SERVICE_INSTANCE_ID => service_instance_id,
32
+ PROCESS_PID => Process.pid
33
+ )
34
+ end
35
+
36
+ def attach_fork_hook
37
+ return if @fork_hook_attached
38
+
39
+ Process.singleton_class.prepend(ForkHook)
40
+ @fork_hook_attached = true
41
+ end
42
+
43
+ def before_fork
44
+ return unless Appsignal::OpenTelemetry.started?
45
+
46
+ providers.transform_values do |provider|
47
+ current = current_resource(provider)
48
+ current && attribute(current, SERVICE_INSTANCE_ID)
49
+ end
50
+ rescue => e
51
+ log_failure("#{e.class}: #{e.message}")
52
+ nil
53
+ end
54
+
55
+ def after_fork(instance_ids_before_fork)
56
+ return unless instance_ids_before_fork
57
+
58
+ update = resource
59
+ providers.each do |name, provider|
60
+ refresh(name, provider, update, instance_ids_before_fork[name])
61
+ end
62
+ rescue => e
63
+ log_failure("#{e.class}: #{e.message}")
64
+ end
65
+
66
+ private
67
+
68
+ def providers
69
+ {
70
+ "tracer" => ::OpenTelemetry.tracer_provider,
71
+ "meter" => ::OpenTelemetry.meter_provider,
72
+ "logger" => ::OpenTelemetry.logger_provider
73
+ }
74
+ end
75
+
76
+ def refresh(name, provider, update, instance_id_before_fork)
77
+ current = current_resource(provider)
78
+ unless current
79
+ log_failure("the #{name} provider's resource could not be read")
80
+ return
81
+ end
82
+ return unless attribute(current, SERVICE_INSTANCE_ID) == instance_id_before_fork
83
+
84
+ refreshed = current.merge(update)
85
+ write_resource(provider, refreshed)
86
+ return if current_resource(provider).equal?(refreshed)
87
+
88
+ log_failure("the #{name} provider did not keep the new resource")
89
+ end
90
+
91
+ # The SDK providers take their resource only in their constructors, and
92
+ # read it from `@resource` when they export.
93
+ def current_resource(provider)
94
+ if provider.respond_to?(:resource)
95
+ resource = provider.resource
96
+ unless provider.respond_to?(:resource=)
97
+ # Without a setter, only `@resource` can be written, and it isn't there.
98
+ return unless provider.instance_variable_defined?(:@resource)
99
+ # The reader returns something else, so writing `@resource` would change nothing.
100
+ return unless provider.instance_variable_get(:@resource).equal?(resource)
101
+ end
102
+ elsif provider.instance_variable_defined?(:@resource)
103
+ resource = provider.instance_variable_get(:@resource)
104
+ end
105
+ # If it's not a resource, it isn't what the provider exports.
106
+ resource if resource.is_a?(::OpenTelemetry::SDK::Resources::Resource)
107
+ end
108
+
109
+ def write_resource(provider, resource)
110
+ if provider.respond_to?(:resource=)
111
+ provider.resource = resource
112
+ else
113
+ provider.instance_variable_set(:@resource, resource)
114
+ end
115
+ end
116
+
117
+ def attribute(resource, key)
118
+ resource.attribute_enumerator.each { |k, v| return v if k == key }
119
+ nil
120
+ end
121
+
122
+ def log_failure(reason)
123
+ Appsignal.internal_logger.warn("#{REFRESH_FAILED}: #{reason}")
124
+ end
125
+ end
126
+
127
+ module ForkHook
128
+ def _fork
129
+ instance_ids = ProcessResource.before_fork
130
+ pid = super
131
+ ProcessResource.after_fork(instance_ids) if pid.zero?
132
+ pid
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -8,6 +8,7 @@ require "appsignal/opentelemetry/http_method"
8
8
  require "appsignal/opentelemetry/http_response"
9
9
  require "appsignal/opentelemetry/http_server_request"
10
10
  require "appsignal/opentelemetry/messaging"
11
+ require "appsignal/opentelemetry/process_resource"
11
12
  require "appsignal/opentelemetry/proxied_exporter"
12
13
  require "appsignal/opentelemetry/rendering"
13
14
  require "appsignal/opentelemetry/sql_db_system"
@@ -59,7 +60,9 @@ module Appsignal
59
60
  # `LoggerProvider` take a `resource:` kwarg that replaces (not
60
61
  # merges), so we do the merge ourselves and use the same merged
61
62
  # resource for the tracer provider to keep all three in sync.
62
- resource = ::OpenTelemetry::SDK::Resources::Resource.default.merge(build_resource(config))
63
+ resource = ProcessResource.resource
64
+ .merge(::OpenTelemetry::SDK::Resources::Resource.default)
65
+ .merge(build_resource(config))
63
66
 
64
67
  span_exporter = build_exporter(
65
68
  ::OpenTelemetry::Exporter::OTLP::Exporter,
@@ -103,6 +106,7 @@ module Appsignal
103
106
  )
104
107
 
105
108
  @started = true
109
+ ProcessResource.attach_fork_hook
106
110
  rescue LoadError => e
107
111
  @started = false
108
112
  Appsignal::Utils::StdoutAndLoggerMessage.error(
@@ -37,6 +37,15 @@ module Appsignal
37
37
  )
38
38
  end
39
39
 
40
+ # @api private
41
+ def self.complete_transaction(transaction)
42
+ safe_execution("Appsignal::Rack::EventHandler#complete_transaction") do
43
+ transaction.complete
44
+ end
45
+ ensure
46
+ Appsignal::Transaction.clear_current_transaction!
47
+ end
48
+
40
49
  # @api private
41
50
  attr_reader :id
42
51
  attr_writer :using_appsignal_event_middleware
@@ -68,29 +77,7 @@ module Appsignal
68
77
  :opentelemetry_context => Appsignal::OpenTelemetry.extract_rack_context(request.env),
69
78
  :opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
70
79
  )
71
- # Describes the transaction's span as an incoming HTTP request.
72
- # Together with the SERVER span kind the transaction already carries,
73
- # this is what the trace timeline reads to recognize a web request.
74
- #
75
- # Set before the event below starts, because attributes go on
76
- # whichever span is open and these belong on the transaction's own
77
- # span. That event stays open until the response finishes, so there is
78
- # no later point in the request at which these could be set.
79
- transaction.add_opentelemetry_attributes(
80
- Appsignal::OpenTelemetry::HttpServerRequest.attributes_for(
81
- :method => Appsignal::Rack::Utils.request_method_from(request),
82
- :path => Appsignal::Rack::Utils.request_value_from(request, :path),
83
- :scheme => Appsignal::Rack::Utils.request_value_from(request, :scheme),
84
- :query => Appsignal::Rack::Utils.request_value_from(request, :query_string),
85
- :host => Appsignal::Rack::Utils.request_value_from(request, :hostname),
86
- :port => Appsignal::Rack::Utils.request_value_from(request, :port),
87
- :protocol =>
88
- Appsignal::Rack::Utils.request_env_value_from(request, "SERVER_PROTOCOL")
89
- )
90
- )
91
- transaction.start_event(
92
- :opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
93
- )
80
+ # Register cleanup before request instrumentation can raise.
94
81
  request.env[APPSIGNAL_TRANSACTION] = transaction
95
82
 
96
83
  request.env[RACK_AFTER_REPLY] ||= []
@@ -113,9 +100,32 @@ module Appsignal
113
100
  # just a fallback if that doesn't get called.
114
101
  #
115
102
  # One such scenario is when a Puma "lowlevel_error" occurs.
116
- transaction.complete
117
- Appsignal::Transaction.clear_current_transaction!
103
+ Appsignal::Rack::EventHandler.complete_transaction(transaction)
118
104
  end
105
+
106
+ # Describes the transaction's span as an incoming HTTP request.
107
+ # Together with the SERVER span kind the transaction already carries,
108
+ # this is what the trace timeline reads to recognize a web request.
109
+ #
110
+ # Set before the event below starts, because attributes go on
111
+ # whichever span is open and these belong on the transaction's own
112
+ # span. That event stays open until the response finishes, so there is
113
+ # no later point in the request at which these could be set.
114
+ transaction.add_opentelemetry_attributes(
115
+ Appsignal::OpenTelemetry::HttpServerRequest.attributes_for(
116
+ :method => Appsignal::Rack::Utils.request_method_from(request),
117
+ :path => Appsignal::Rack::Utils.request_value_from(request, :path),
118
+ :scheme => Appsignal::Rack::Utils.request_value_from(request, :scheme),
119
+ :query => Appsignal::Rack::Utils.request_value_from(request, :query_string),
120
+ :host => Appsignal::Rack::Utils.request_value_from(request, :hostname),
121
+ :port => Appsignal::Rack::Utils.request_value_from(request, :port),
122
+ :protocol =>
123
+ Appsignal::Rack::Utils.request_env_value_from(request, "SERVER_PROTOCOL")
124
+ )
125
+ )
126
+ transaction.start_event(
127
+ :opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
128
+ )
119
129
  end
120
130
  end
121
131
 
@@ -188,8 +198,7 @@ module Appsignal
188
198
 
189
199
  # Make sure the current transaction is always closed when the request
190
200
  # is finished
191
- transaction.complete
192
- Appsignal::Transaction.clear_current_transaction!
201
+ self.class.complete_transaction(transaction)
193
202
  end
194
203
 
195
204
  private
@@ -446,6 +446,7 @@ module Appsignal
446
446
  emit_queue_duration_metric if should_report?
447
447
  report_allocation_count
448
448
  end
449
+ ensure
449
450
  teardown
450
451
  end
451
452
 
@@ -316,26 +316,30 @@ module Appsignal
316
316
  return
317
317
  end
318
318
 
319
- # If the transaction is a duplicate, we don't want to finish it,
320
- # because we want its finish time to be the finish time of the
321
- # original transaction.
322
- # Duplicate transactions should always be sampled, as we only
323
- # create duplicates for errors, which are always sampled.
324
- should_sample = true
325
-
326
- unless duplicate?
327
- self.class.last_errors = @errors.to_a
328
- should_sample = @backend.finish
329
- end
330
-
331
- report_errors
319
+ begin
320
+ # If the transaction is a duplicate, we don't want to finish it,
321
+ # because we want its finish time to be the finish time of the
322
+ # original transaction.
323
+ # Duplicate transactions should always be sampled, as we only
324
+ # create duplicates for errors, which are always sampled.
325
+ should_sample = true
326
+
327
+ unless duplicate?
328
+ self.class.last_errors = @errors.to_a
329
+ should_sample = @backend.finish
330
+ end
332
331
 
333
- run_before_complete_hooks
332
+ report_errors
334
333
 
335
- sample_data if should_sample
334
+ run_before_complete_hooks
336
335
 
337
- @completed = true
338
- @backend.complete
336
+ sample_data if should_sample
337
+ ensure
338
+ # Release backend resources even when sample data cannot be collected
339
+ # or serialized. In collector mode this also detaches the span context.
340
+ @completed = true
341
+ @backend.complete
342
+ end
339
343
  end
340
344
 
341
345
  # @!visibility private
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Appsignal
4
4
  # @return [String]
5
- VERSION = "5.0.2"
5
+ VERSION = "5.0.4"
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("5.0.2", T.untyped)
11
+ VERSION = T.let("5.0.4", 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: 5.0.2
4
+ version: 5.0.4
5
5
  platform: java
6
6
  authors:
7
7
  - Robert Beekman
@@ -306,6 +306,7 @@ files:
306
306
  - lib/appsignal/opentelemetry/http_response.rb
307
307
  - lib/appsignal/opentelemetry/http_server_request.rb
308
308
  - lib/appsignal/opentelemetry/messaging.rb
309
+ - lib/appsignal/opentelemetry/process_resource.rb
309
310
  - lib/appsignal/opentelemetry/proxied_exporter.rb
310
311
  - lib/appsignal/opentelemetry/rendering.rb
311
312
  - lib/appsignal/opentelemetry/sql_db_system.rb