appsignal 5.0.2 → 5.0.4
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 +16 -0
- data/lib/appsignal/opentelemetry/process_resource.rb +137 -0
- data/lib/appsignal/opentelemetry.rb +5 -1
- data/lib/appsignal/rack/event_handler.rb +36 -27
- data/lib/appsignal/transaction/opentelemetry_backend.rb +1 -0
- data/lib/appsignal/transaction.rb +21 -17
- data/lib/appsignal/version.rb +1 -1
- data/sig/appsignal.rbi +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9297a2ba1c7e66ca0b87fb0e905576fe99c7ff197a591abfbd13d05c79f75ba3
|
|
4
|
+
data.tar.gz: 94ca97dc758b43867456f9ca351085ba0f43945d55c94ef777d83af89a77c2d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 133d0b1b1ed03a1ab40eb3cbc56e37171d50d4c1699727fe5e145be3b5af98b3fd87f587abae49918bd967ceebb0c7918f58b416b6b63870db843f7d79adb1b9
|
|
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 =
|
|
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
|
-
#
|
|
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
|
|
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
|
|
192
|
-
Appsignal::Transaction.clear_current_transaction!
|
|
201
|
+
self.class.complete_transaction(transaction)
|
|
193
202
|
end
|
|
194
203
|
|
|
195
204
|
private
|
|
@@ -316,26 +316,30 @@ module Appsignal
|
|
|
316
316
|
return
|
|
317
317
|
end
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
332
|
+
report_errors
|
|
334
333
|
|
|
335
|
-
|
|
334
|
+
run_before_complete_hooks
|
|
336
335
|
|
|
337
|
-
|
|
338
|
-
|
|
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
|
data/lib/appsignal/version.rb
CHANGED
data/sig/appsignal.rbi
CHANGED
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.
|
|
4
|
+
version: 5.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Beekman
|
|
@@ -292,6 +292,7 @@ files:
|
|
|
292
292
|
- lib/appsignal/opentelemetry/http_response.rb
|
|
293
293
|
- lib/appsignal/opentelemetry/http_server_request.rb
|
|
294
294
|
- lib/appsignal/opentelemetry/messaging.rb
|
|
295
|
+
- lib/appsignal/opentelemetry/process_resource.rb
|
|
295
296
|
- lib/appsignal/opentelemetry/proxied_exporter.rb
|
|
296
297
|
- lib/appsignal/opentelemetry/rendering.rb
|
|
297
298
|
- lib/appsignal/opentelemetry/sql_db_system.rb
|