appsignal 4.10.4-java → 5.0.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 +108 -0
- data/Rakefile +195 -4
- data/appsignal.gemspec +8 -0
- data/build_matrix.yml +12 -0
- data/ext/appsignal_extension.c +14 -0
- data/lib/appsignal/backends.rb +55 -0
- data/lib/appsignal/cli/diagnose.rb +2 -10
- data/lib/appsignal/config.rb +377 -13
- data/lib/appsignal/demo.rb +12 -10
- data/lib/appsignal/event_formatter/action_view/render_formatter.rb +34 -22
- data/lib/appsignal/event_formatter/active_job/perform_formatter.rb +35 -0
- data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +19 -0
- data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +27 -0
- data/lib/appsignal/event_formatter/recorded_elsewhere.rb +17 -0
- data/lib/appsignal/event_formatter/rom/sql_formatter.rb +24 -0
- data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +5 -0
- data/lib/appsignal/event_formatter/view_component/render_formatter.rb +21 -10
- data/lib/appsignal/event_formatter.rb +78 -0
- data/lib/appsignal/extension.rb +4 -0
- data/lib/appsignal/helpers/instrumentation.rb +324 -20
- data/lib/appsignal/helpers/metrics.rb +3 -24
- data/lib/appsignal/hooks/action_cable.rb +26 -8
- data/lib/appsignal/hooks/active_job.rb +184 -47
- data/lib/appsignal/hooks/at_exit.rb +4 -1
- data/lib/appsignal/hooks/excon.rb +20 -0
- data/lib/appsignal/hooks/faraday.rb +16 -0
- data/lib/appsignal/hooks/http.rb +5 -0
- data/lib/appsignal/hooks/resque.rb +1 -1
- data/lib/appsignal/hooks/sequel.rb +32 -2
- data/lib/appsignal/hooks/shoryuken.rb +3 -3
- data/lib/appsignal/hooks/sidekiq.rb +1 -1
- data/lib/appsignal/integrations/action_cable.rb +5 -2
- data/lib/appsignal/integrations/active_support_notifications.rb +59 -19
- data/lib/appsignal/integrations/data_mapper.rb +14 -2
- data/lib/appsignal/integrations/delayed_job_plugin.rb +81 -8
- data/lib/appsignal/integrations/dry_monitor.rb +39 -15
- data/lib/appsignal/integrations/excon/appsignal_middleware.rb +21 -0
- data/lib/appsignal/integrations/excon.rb +52 -15
- data/lib/appsignal/integrations/faraday.rb +47 -12
- data/lib/appsignal/integrations/http.rb +43 -1
- data/lib/appsignal/integrations/mongo_ruby_driver.rb +73 -4
- data/lib/appsignal/integrations/net_http.rb +31 -2
- data/lib/appsignal/integrations/puma.rb +4 -1
- data/lib/appsignal/integrations/que.rb +256 -37
- data/lib/appsignal/integrations/railtie.rb +4 -1
- data/lib/appsignal/integrations/rake.rb +9 -3
- data/lib/appsignal/integrations/redis.rb +22 -1
- data/lib/appsignal/integrations/redis_client.rb +22 -1
- data/lib/appsignal/integrations/resque.rb +81 -11
- data/lib/appsignal/integrations/shoryuken.rb +159 -12
- data/lib/appsignal/integrations/sidekiq.rb +94 -16
- data/lib/appsignal/integrations/webmachine.rb +56 -5
- data/lib/appsignal/loaders/padrino.rb +2 -1
- data/lib/appsignal/logger/extension_backend.rb +24 -0
- data/lib/appsignal/logger/opentelemetry_backend.rb +66 -0
- data/lib/appsignal/logger.rb +13 -9
- data/lib/appsignal/metrics/extension_backend.rb +47 -0
- data/lib/appsignal/metrics/opentelemetry_backend.rb +89 -0
- data/lib/appsignal/opentelemetry/attributes.rb +31 -0
- data/lib/appsignal/opentelemetry/dependencies.rb +35 -0
- data/lib/appsignal/opentelemetry/error_type.rb +37 -0
- data/lib/appsignal/opentelemetry/http_client_request.rb +83 -0
- data/lib/appsignal/opentelemetry/http_method.rb +59 -0
- data/lib/appsignal/opentelemetry/http_response.rb +30 -0
- data/lib/appsignal/opentelemetry/http_server_request.rb +79 -0
- data/lib/appsignal/opentelemetry/messaging.rb +82 -0
- data/lib/appsignal/opentelemetry/proxied_exporter.rb +83 -0
- data/lib/appsignal/opentelemetry/rendering.rb +29 -0
- data/lib/appsignal/opentelemetry/sql_db_system.rb +89 -0
- data/lib/appsignal/opentelemetry.rb +495 -0
- data/lib/appsignal/rack/abstract_middleware.rb +66 -4
- data/lib/appsignal/rack/body_wrapper.rb +18 -5
- data/lib/appsignal/rack/event_handler.rb +44 -4
- data/lib/appsignal/rack/grape_middleware.rb +1 -0
- data/lib/appsignal/rack/hanami_middleware.rb +2 -1
- data/lib/appsignal/rack/instrumentation_middleware.rb +1 -0
- data/lib/appsignal/rack/rails_instrumentation.rb +1 -0
- data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -0
- data/lib/appsignal/rack.rb +68 -12
- data/lib/appsignal/sample_data.rb +4 -0
- data/lib/appsignal/transaction/base_backend.rb +128 -0
- data/lib/appsignal/transaction/extension_backend.rb +229 -0
- data/lib/appsignal/transaction/opentelemetry_backend.rb +847 -0
- data/lib/appsignal/transaction.rb +714 -164
- data/lib/appsignal/utils/request_headers.rb +78 -0
- data/lib/appsignal/utils/stdout_and_logger_message.rb +9 -0
- data/lib/appsignal/utils.rb +1 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +10 -0
- data/sig/appsignal.rbi +630 -37
- data/sig/appsignal.rbs +582 -27
- metadata +25 -1
|
@@ -20,6 +20,10 @@ module Appsignal
|
|
|
20
20
|
@options = options
|
|
21
21
|
@request_class = options.fetch(:request_class, ::Rack::Request)
|
|
22
22
|
@instrument_event_name = options.fetch(:instrument_event_name, nil)
|
|
23
|
+
# The OpenTelemetry instrumentation scope for the request's spans in
|
|
24
|
+
# collector mode. Each framework middleware sets its own; nil falls back
|
|
25
|
+
# to the default scope in the backend.
|
|
26
|
+
@opentelemetry_scope = options.fetch(:opentelemetry_scope, nil)
|
|
23
27
|
@report_errors = options.fetch(:report_errors, DEFAULT_ERROR_REPORTING)
|
|
24
28
|
end
|
|
25
29
|
|
|
@@ -33,7 +37,11 @@ module Appsignal
|
|
|
33
37
|
if wrapped_instrumentation
|
|
34
38
|
env[Appsignal::Rack::APPSIGNAL_TRANSACTION]
|
|
35
39
|
else
|
|
36
|
-
Appsignal::Transaction.create(
|
|
40
|
+
Appsignal::Transaction.create(
|
|
41
|
+
Appsignal::Transaction::HTTP_REQUEST,
|
|
42
|
+
:opentelemetry_context => Appsignal::OpenTelemetry.extract_rack_context(env),
|
|
43
|
+
:opentelemetry_scope => @opentelemetry_scope
|
|
44
|
+
)
|
|
37
45
|
end
|
|
38
46
|
|
|
39
47
|
unless wrapped_instrumentation
|
|
@@ -41,6 +49,7 @@ module Appsignal
|
|
|
41
49
|
# middleware can detect if there is parent instrumentation
|
|
42
50
|
# middleware active.
|
|
43
51
|
env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = transaction
|
|
52
|
+
add_opentelemetry_request_attributes(transaction, request)
|
|
44
53
|
end
|
|
45
54
|
|
|
46
55
|
begin
|
|
@@ -59,8 +68,13 @@ module Appsignal
|
|
|
59
68
|
ensure
|
|
60
69
|
add_transaction_metadata_after(transaction, request)
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
unless wrapped_instrumentation
|
|
72
|
+
add_opentelemetry_response_attributes(transaction, request)
|
|
73
|
+
|
|
74
|
+
# Complete transaction because this is the top instrumentation
|
|
75
|
+
# middleware.
|
|
76
|
+
Appsignal::Transaction.complete_current!
|
|
77
|
+
end
|
|
64
78
|
end
|
|
65
79
|
else
|
|
66
80
|
@app.call(env)
|
|
@@ -69,6 +83,48 @@ module Appsignal
|
|
|
69
83
|
|
|
70
84
|
private
|
|
71
85
|
|
|
86
|
+
# Describes the transaction's span as an incoming HTTP request. Together
|
|
87
|
+
# with the SERVER span kind the transaction already carries, this is what
|
|
88
|
+
# the trace timeline reads to recognize a web request.
|
|
89
|
+
#
|
|
90
|
+
# Set where the transaction is created, rather than with the rest of the
|
|
91
|
+
# request metadata in {ApplyRackRequest}, which runs after the app has been
|
|
92
|
+
# called: by then an event span may be open, and the attributes would land
|
|
93
|
+
# on that instead. Only the middleware that created the transaction sets
|
|
94
|
+
# them, so nested middleware does not write them again.
|
|
95
|
+
def add_opentelemetry_request_attributes(transaction, request)
|
|
96
|
+
transaction.add_opentelemetry_attributes(
|
|
97
|
+
Appsignal::OpenTelemetry::HttpServerRequest.attributes_for(
|
|
98
|
+
:method => Appsignal::Rack::Utils.request_method_from(request),
|
|
99
|
+
:path => Appsignal::Rack::Utils.request_value_from(request, :path),
|
|
100
|
+
:scheme => Appsignal::Rack::Utils.request_value_from(request, :scheme),
|
|
101
|
+
:query => Appsignal::Rack::Utils.request_value_from(request, :query_string),
|
|
102
|
+
:host => Appsignal::Rack::Utils.request_value_from(request, :hostname),
|
|
103
|
+
:port => Appsignal::Rack::Utils.request_value_from(request, :port),
|
|
104
|
+
:protocol => Appsignal::Rack::Utils.request_env_value_from(request, "SERVER_PROTOCOL")
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Describes the response the app produced on the transaction's span, which
|
|
110
|
+
# the semantic conventions ask for whenever a response was sent.
|
|
111
|
+
#
|
|
112
|
+
# Set from the `ensure` in {#call} rather than from {#call_app}, where the
|
|
113
|
+
# status is first known. That runs inside the instrumented event, so the
|
|
114
|
+
# attribute would land on the event span instead of on the transaction's
|
|
115
|
+
# own span. The status travels between the two in the request environment
|
|
116
|
+
# for that reason.
|
|
117
|
+
#
|
|
118
|
+
# A request whose app raised never produced a status, and is described
|
|
119
|
+
# without one.
|
|
120
|
+
def add_opentelemetry_response_attributes(transaction, request)
|
|
121
|
+
transaction.add_opentelemetry_attributes(
|
|
122
|
+
Appsignal::OpenTelemetry::HttpResponse.attributes_for(
|
|
123
|
+
request.env[Appsignal::Rack::APPSIGNAL_RESPONSE_STATUS]
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
72
128
|
# Another instrumentation middleware is active earlier in the stack, so
|
|
73
129
|
# don't report any exceptions here, the top instrumentation middleware
|
|
74
130
|
# will be the one reporting the exception.
|
|
@@ -79,7 +135,10 @@ module Appsignal
|
|
|
79
135
|
# @see #instrument_app_call_with_exception_handling
|
|
80
136
|
def instrument_app_call(env, transaction)
|
|
81
137
|
if @instrument_event_name
|
|
82
|
-
Appsignal.instrument(
|
|
138
|
+
Appsignal.instrument(
|
|
139
|
+
@instrument_event_name,
|
|
140
|
+
:opentelemetry_scope => @opentelemetry_scope
|
|
141
|
+
) do
|
|
83
142
|
call_app(env, transaction)
|
|
84
143
|
end
|
|
85
144
|
else
|
|
@@ -89,6 +148,9 @@ module Appsignal
|
|
|
89
148
|
|
|
90
149
|
def call_app(env, transaction)
|
|
91
150
|
status, headers, obody = @app.call(env)
|
|
151
|
+
# Remember the status for {#add_opentelemetry_response_attributes}, which
|
|
152
|
+
# runs once this event has closed.
|
|
153
|
+
env[Appsignal::Rack::APPSIGNAL_RESPONSE_STATUS] = status
|
|
92
154
|
body =
|
|
93
155
|
if env[Appsignal::Rack::APPSIGNAL_RESPONSE_INSTRUMENTED]
|
|
94
156
|
obody
|
|
@@ -48,7 +48,10 @@ module Appsignal
|
|
|
48
48
|
# of the body has already closed itself (as prescribed) we do not
|
|
49
49
|
# attempt to close it twice
|
|
50
50
|
if !@body_already_closed && @body.respond_to?(:close)
|
|
51
|
-
Appsignal.instrument(
|
|
51
|
+
Appsignal.instrument(
|
|
52
|
+
"close_response_body.rack",
|
|
53
|
+
:opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
|
|
54
|
+
) { @body.close }
|
|
52
55
|
end
|
|
53
56
|
@body_already_closed = true
|
|
54
57
|
rescue *IGNORED_ERRORS # Do not report
|
|
@@ -104,7 +107,11 @@ module Appsignal
|
|
|
104
107
|
# in a blockless way it is still a good idea to have it in place.
|
|
105
108
|
return enum_for(:each) unless block_given?
|
|
106
109
|
|
|
107
|
-
Appsignal.instrument(
|
|
110
|
+
Appsignal.instrument(
|
|
111
|
+
"process_response_body.rack",
|
|
112
|
+
"Process Rack response body (#each)",
|
|
113
|
+
:opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
|
|
114
|
+
) do
|
|
108
115
|
@body.each(&blk)
|
|
109
116
|
end
|
|
110
117
|
rescue *IGNORED_ERRORS # Do not report
|
|
@@ -125,7 +132,11 @@ module Appsignal
|
|
|
125
132
|
def call(stream)
|
|
126
133
|
# `stream` will be closed by the app we are calling, no need for us
|
|
127
134
|
# to close it ourselves
|
|
128
|
-
Appsignal.instrument(
|
|
135
|
+
Appsignal.instrument(
|
|
136
|
+
"process_response_body.rack",
|
|
137
|
+
"Process Rack response body (#call)",
|
|
138
|
+
:opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
|
|
139
|
+
) do
|
|
129
140
|
@body.call(stream)
|
|
130
141
|
end
|
|
131
142
|
rescue *IGNORED_ERRORS # Do not report
|
|
@@ -150,7 +161,8 @@ module Appsignal
|
|
|
150
161
|
@body_already_closed = true
|
|
151
162
|
Appsignal.instrument(
|
|
152
163
|
"process_response_body.rack",
|
|
153
|
-
"Process Rack response body (#to_ary)"
|
|
164
|
+
"Process Rack response body (#to_ary)",
|
|
165
|
+
:opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
|
|
154
166
|
) do
|
|
155
167
|
@body.to_ary
|
|
156
168
|
end
|
|
@@ -168,7 +180,8 @@ module Appsignal
|
|
|
168
180
|
def to_path
|
|
169
181
|
Appsignal.instrument(
|
|
170
182
|
"process_response_body.rack",
|
|
171
|
-
"Process Rack response body (#to_path)"
|
|
183
|
+
"Process Rack response body (#to_path)",
|
|
184
|
+
:opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
|
|
172
185
|
) do
|
|
173
186
|
@body.to_path
|
|
174
187
|
end
|
|
@@ -63,8 +63,34 @@ module Appsignal
|
|
|
63
63
|
request.env[APPSIGNAL_EVENT_HANDLER_ID] ||= id
|
|
64
64
|
return unless request_handler?(request.env[APPSIGNAL_EVENT_HANDLER_ID])
|
|
65
65
|
|
|
66
|
-
transaction = Appsignal::Transaction.create(
|
|
67
|
-
|
|
66
|
+
transaction = Appsignal::Transaction.create(
|
|
67
|
+
Appsignal::Transaction::HTTP_REQUEST,
|
|
68
|
+
:opentelemetry_context => Appsignal::OpenTelemetry.extract_rack_context(request.env),
|
|
69
|
+
:opentelemetry_scope => ["appsignal-ruby/rack", Appsignal::VERSION]
|
|
70
|
+
)
|
|
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
|
+
)
|
|
68
94
|
request.env[APPSIGNAL_TRANSACTION] = transaction
|
|
69
95
|
|
|
70
96
|
request.env[RACK_AFTER_REPLY] ||= []
|
|
@@ -118,13 +144,27 @@ module Appsignal
|
|
|
118
144
|
|
|
119
145
|
self.class.safe_execution("Appsignal::Rack::EventHandler#on_finish") do
|
|
120
146
|
transaction.finish_event("process_request.rack", "callback: on_finish", "")
|
|
121
|
-
transaction.
|
|
122
|
-
|
|
147
|
+
transaction.add_request_payload_if_nil { request.params }
|
|
148
|
+
headers, environment =
|
|
149
|
+
Appsignal::Utils::RequestHeaders.split_lazily { request.env }
|
|
150
|
+
transaction.add_request_headers_if_nil(&headers)
|
|
151
|
+
transaction.add_request_environment_if_nil(&environment)
|
|
123
152
|
transaction.add_session_data_if_nil do
|
|
124
153
|
request.session if request.respond_to?(:session)
|
|
125
154
|
end
|
|
126
155
|
queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
|
|
127
156
|
transaction.set_queue_start(queue_start) if queue_start
|
|
157
|
+
# Describes the response on the transaction's span, which the semantic
|
|
158
|
+
# conventions ask for whenever a response was sent. It can be set here
|
|
159
|
+
# because the `process_request.rack` event was finished above, which
|
|
160
|
+
# leaves the transaction's own span as the one attributes go on.
|
|
161
|
+
#
|
|
162
|
+
# Only a response the app actually produced counts. The 500 below
|
|
163
|
+
# stands in for a status that was never sent, so it is reported as a
|
|
164
|
+
# tag and a metric but not as this attribute.
|
|
165
|
+
transaction.add_opentelemetry_attributes(
|
|
166
|
+
Appsignal::OpenTelemetry::HttpResponse.attributes_for(response&.status)
|
|
167
|
+
)
|
|
128
168
|
response_status =
|
|
129
169
|
if response
|
|
130
170
|
response.status
|
|
@@ -7,6 +7,7 @@ module Appsignal
|
|
|
7
7
|
# @api private
|
|
8
8
|
def initialize(app, options = {})
|
|
9
9
|
options[:instrument_event_name] = "process_request.grape"
|
|
10
|
+
options[:opentelemetry_scope] = ["appsignal-ruby/grape", Appsignal::VERSION]
|
|
10
11
|
options[:report_errors] = lambda { |env| !env["grape.skip_appsignal_error"] }
|
|
11
12
|
super
|
|
12
13
|
end
|
|
@@ -7,6 +7,7 @@ module Appsignal
|
|
|
7
7
|
def initialize(app, options = {})
|
|
8
8
|
options[:params_method] = nil
|
|
9
9
|
options[:instrument_event_name] ||= "process_action.hanami"
|
|
10
|
+
options[:opentelemetry_scope] ||= ["appsignal-ruby/hanami", Appsignal::VERSION]
|
|
10
11
|
super
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -19,7 +20,7 @@ module Appsignal
|
|
|
19
20
|
def add_transaction_metadata_after(transaction, request)
|
|
20
21
|
action_name = fetch_hanami_action(request.env)
|
|
21
22
|
transaction.set_action_if_nil(action_name) if action_name
|
|
22
|
-
transaction.
|
|
23
|
+
transaction.add_request_payload { params_for(request) }
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def params_for(request)
|
|
@@ -55,6 +55,7 @@ module Appsignal
|
|
|
55
55
|
class InstrumentationMiddleware < AbstractMiddleware
|
|
56
56
|
def initialize(app, options = {})
|
|
57
57
|
options[:instrument_event_name] ||= "process_request_middleware.rack"
|
|
58
|
+
options[:opentelemetry_scope] ||= ["appsignal-ruby/rack", Appsignal::VERSION]
|
|
58
59
|
super
|
|
59
60
|
end
|
|
60
61
|
end
|
|
@@ -8,6 +8,7 @@ module Appsignal
|
|
|
8
8
|
options[:request_class] ||= ActionDispatch::Request
|
|
9
9
|
options[:params_method] ||= :filtered_parameters
|
|
10
10
|
options[:instrument_event_name] = nil
|
|
11
|
+
options[:opentelemetry_scope] = ["appsignal-ruby/rails", Appsignal::VERSION]
|
|
11
12
|
options[:report_errors] = true
|
|
12
13
|
super
|
|
13
14
|
end
|
|
@@ -33,6 +33,7 @@ module Appsignal
|
|
|
33
33
|
options[:request_class] ||= Sinatra::Request
|
|
34
34
|
options[:params_method] ||= :params
|
|
35
35
|
options[:instrument_event_name] ||= "process_action.sinatra"
|
|
36
|
+
options[:opentelemetry_scope] ||= ["appsignal-ruby/sinatra", Appsignal::VERSION]
|
|
36
37
|
super
|
|
37
38
|
@raise_errors_on = raise_errors?(app)
|
|
38
39
|
end
|
data/lib/appsignal/rack.rb
CHANGED
|
@@ -7,9 +7,72 @@ module Appsignal
|
|
|
7
7
|
APPSIGNAL_EVENT_HANDLER_ID = "appsignal.event_handler_id"
|
|
8
8
|
APPSIGNAL_EVENT_HANDLER_HAS_ERROR = "appsignal.event_handler.error"
|
|
9
9
|
APPSIGNAL_RESPONSE_INSTRUMENTED = "appsignal.response_instrumentation_active"
|
|
10
|
+
APPSIGNAL_RESPONSE_STATUS = "appsignal.response_status"
|
|
10
11
|
RACK_AFTER_REPLY = "rack.after_reply"
|
|
11
12
|
|
|
12
13
|
class Utils
|
|
14
|
+
# Fetch the HTTP request method from the request.
|
|
15
|
+
#
|
|
16
|
+
# The request class is configurable, so reading the method can raise.
|
|
17
|
+
# Log and return nil in that case, leaving it to the caller to skip
|
|
18
|
+
# whatever it needed the method for.
|
|
19
|
+
#
|
|
20
|
+
# @param request [Rack::Request] Request object.
|
|
21
|
+
# @return [String, NilClass]
|
|
22
|
+
def self.request_method_from(request)
|
|
23
|
+
request.request_method
|
|
24
|
+
rescue => error
|
|
25
|
+
Appsignal.internal_logger.error(
|
|
26
|
+
"Exception while fetching the HTTP request method: #{error.class}: #{error}"
|
|
27
|
+
)
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Fetch a value that describes the request, named after the method that
|
|
32
|
+
# reads it.
|
|
33
|
+
#
|
|
34
|
+
# The request class is configurable, so reading from the request can
|
|
35
|
+
# raise. Log and return nil in that case, leaving it to the caller to skip
|
|
36
|
+
# whatever it needed the value for.
|
|
37
|
+
#
|
|
38
|
+
# @param request [Rack::Request] Request object.
|
|
39
|
+
# @param name [Symbol] Name of the method that reads the value.
|
|
40
|
+
# @return [Object, NilClass]
|
|
41
|
+
def self.request_value_from(request, name)
|
|
42
|
+
request.public_send(name)
|
|
43
|
+
rescue => error
|
|
44
|
+
Appsignal.internal_logger.error(
|
|
45
|
+
"Exception while fetching the HTTP request #{name}: #{error.class}: #{error}"
|
|
46
|
+
)
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Fetch a value from the request environment, for the values that are only
|
|
51
|
+
# available there.
|
|
52
|
+
#
|
|
53
|
+
# The request class is configurable, so it may have no environment at all,
|
|
54
|
+
# which is not worth logging about. Reading from one can still raise, and
|
|
55
|
+
# that is logged. Either way the caller gets nil and skips whatever it
|
|
56
|
+
# needed the value for.
|
|
57
|
+
#
|
|
58
|
+
# @param request [Rack::Request] Request object.
|
|
59
|
+
# @param key [String] Name of the environment key to read.
|
|
60
|
+
# @return [Object, NilClass]
|
|
61
|
+
def self.request_env_value_from(request, key)
|
|
62
|
+
return unless request.respond_to?(:env)
|
|
63
|
+
|
|
64
|
+
env = request.env
|
|
65
|
+
return unless env
|
|
66
|
+
|
|
67
|
+
env[key]
|
|
68
|
+
rescue => error
|
|
69
|
+
Appsignal.internal_logger.error(
|
|
70
|
+
"Exception while fetching the HTTP request environment #{key}: " \
|
|
71
|
+
"#{error.class}: #{error}"
|
|
72
|
+
)
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
13
76
|
# Fetch the queue start time from the request environment.
|
|
14
77
|
#
|
|
15
78
|
# @since 3.11.0
|
|
@@ -53,18 +116,20 @@ module Appsignal
|
|
|
53
116
|
# TODO: Remove in next major/minor version
|
|
54
117
|
transaction.set_metadata("path", request_path)
|
|
55
118
|
|
|
56
|
-
request_method =
|
|
119
|
+
request_method = Appsignal::Rack::Utils.request_method_from(request)
|
|
57
120
|
if request_method
|
|
58
121
|
transaction.set_metadata("request_method", request_method)
|
|
59
122
|
# TODO: Remove in next major/minor version
|
|
60
123
|
transaction.set_metadata("method", request_method)
|
|
61
124
|
end
|
|
62
125
|
|
|
63
|
-
transaction.
|
|
126
|
+
transaction.add_request_payload { params_for(request) }
|
|
64
127
|
transaction.add_session_data { session_data_for(request) }
|
|
65
|
-
|
|
128
|
+
headers, environment = Appsignal::Utils::RequestHeaders.split_lazily do
|
|
66
129
|
request.env if request.respond_to?(:env)
|
|
67
130
|
end
|
|
131
|
+
transaction.add_request_headers(&headers)
|
|
132
|
+
transaction.add_request_environment(&environment)
|
|
68
133
|
|
|
69
134
|
queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
|
|
70
135
|
transaction.set_queue_start(queue_start) if queue_start
|
|
@@ -84,15 +149,6 @@ module Appsignal
|
|
|
84
149
|
nil
|
|
85
150
|
end
|
|
86
151
|
|
|
87
|
-
def request_method_for(request)
|
|
88
|
-
request.request_method
|
|
89
|
-
rescue => error
|
|
90
|
-
Appsignal.internal_logger.error(
|
|
91
|
-
"Exception while fetching the HTTP request method: #{error.class}: #{error}"
|
|
92
|
-
)
|
|
93
|
-
nil
|
|
94
|
-
end
|
|
95
|
-
|
|
96
152
|
def session_data_for(request)
|
|
97
153
|
return unless request.respond_to?(:session)
|
|
98
154
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
class Transaction
|
|
5
|
+
# @!visibility private
|
|
6
|
+
#
|
|
7
|
+
# The interface every transaction backend implements. `Appsignal::Backends
|
|
8
|
+
# .transaction` picks a concrete backend per mode -- ExtensionBackend in
|
|
9
|
+
# agent mode, OpenTelemetryBackend in collector mode. This base documents the
|
|
10
|
+
# contract; a backend that leaves a method unimplemented raises here.
|
|
11
|
+
class BaseBackend
|
|
12
|
+
# Instrumented events.
|
|
13
|
+
def start_event(opentelemetry_kind: nil, opentelemetry_scope: nil)
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def finish_event(_name, _title, _body, _body_format)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def record_event( # rubocop:disable Metrics/ParameterLists
|
|
22
|
+
_name, _title, _body, _body_format, _duration,
|
|
23
|
+
opentelemetry_kind: nil, opentelemetry_scope: nil
|
|
24
|
+
)
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Transaction metadata.
|
|
29
|
+
def set_action(_action)
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def set_namespace(_namespace)
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_metadata(_key, _value)
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def set_queue_start(_start)
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# OpenTelemetry span attributes, set on whichever span the backend is
|
|
46
|
+
# currently recording. Only meaningful in collector mode; agent mode has
|
|
47
|
+
# no span to hang them on.
|
|
48
|
+
def set_attributes(_attributes)
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Maps each logical params channel (`:params`, `:request_payload`,
|
|
53
|
+
# `:function_parameters`) to the storage bucket it lands in. Channels that
|
|
54
|
+
# share a bucket merge into one `SampleData` object on the transaction;
|
|
55
|
+
# distinct buckets stay separate. The bucket name is also the sample-data
|
|
56
|
+
# key `set_sample_data` receives for that bucket, so it must be a key this
|
|
57
|
+
# backend's `set_sample_data` knows how to store.
|
|
58
|
+
def params_mapping
|
|
59
|
+
raise NotImplementedError
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Maps each params bucket to the configuration options that decide what
|
|
63
|
+
# it reports: `:filter`, naming the keys to filter out of it, and
|
|
64
|
+
# `:send`, deciding whether to report it at all.
|
|
65
|
+
def params_options
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Maps each logical header channel (`:request_headers`,
|
|
70
|
+
# `:request_environment`) to the storage bucket it lands in and the
|
|
71
|
+
# transform to apply to each key and value added on it, as
|
|
72
|
+
# `[bucket, transform]`. A `nil` transform leaves the value alone.
|
|
73
|
+
def headers_mapping
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Maps each header bucket to the configuration option that lists the
|
|
78
|
+
# keys to keep in it. An option holding `nil` keeps every key.
|
|
79
|
+
def headers_allowlist
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Sample data (params, session, tags, ...), breadcrumbs and errors.
|
|
84
|
+
def set_sample_data(_key, _data)
|
|
85
|
+
raise NotImplementedError
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def add_breadcrumb(_breadcrumb)
|
|
89
|
+
raise NotImplementedError
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def set_error(_class_name, _message, _backtrace, _causes, _root_cause_missing)
|
|
93
|
+
raise NotImplementedError
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Whether the backend can hold more than one error on a single
|
|
97
|
+
# transaction. When it can't (agent mode), the Transaction reports the
|
|
98
|
+
# extra errors as duplicate transactions instead.
|
|
99
|
+
def supports_multiple_errors?
|
|
100
|
+
raise NotImplementedError
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Lifecycle.
|
|
104
|
+
def finish
|
|
105
|
+
raise NotImplementedError
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def complete
|
|
109
|
+
raise NotImplementedError
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def discard
|
|
113
|
+
raise NotImplementedError
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Only used when `supports_multiple_errors?` is false (agent mode).
|
|
117
|
+
# Backends that support multiple errors never duplicate and leave this
|
|
118
|
+
# unimplemented.
|
|
119
|
+
def duplicate(_new_transaction_id)
|
|
120
|
+
raise NotImplementedError
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def to_json # rubocop:disable Lint/ToJSON
|
|
124
|
+
raise NotImplementedError
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|