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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -0
  3. data/Rakefile +195 -4
  4. data/appsignal.gemspec +8 -0
  5. data/build_matrix.yml +12 -0
  6. data/ext/appsignal_extension.c +14 -0
  7. data/lib/appsignal/backends.rb +55 -0
  8. data/lib/appsignal/cli/diagnose.rb +2 -10
  9. data/lib/appsignal/config.rb +377 -13
  10. data/lib/appsignal/demo.rb +12 -10
  11. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +34 -22
  12. data/lib/appsignal/event_formatter/active_job/perform_formatter.rb +35 -0
  13. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +19 -0
  14. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +27 -0
  15. data/lib/appsignal/event_formatter/recorded_elsewhere.rb +17 -0
  16. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +24 -0
  17. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +5 -0
  18. data/lib/appsignal/event_formatter/view_component/render_formatter.rb +21 -10
  19. data/lib/appsignal/event_formatter.rb +78 -0
  20. data/lib/appsignal/extension.rb +4 -0
  21. data/lib/appsignal/helpers/instrumentation.rb +324 -20
  22. data/lib/appsignal/helpers/metrics.rb +3 -24
  23. data/lib/appsignal/hooks/action_cable.rb +26 -8
  24. data/lib/appsignal/hooks/active_job.rb +184 -47
  25. data/lib/appsignal/hooks/at_exit.rb +4 -1
  26. data/lib/appsignal/hooks/excon.rb +20 -0
  27. data/lib/appsignal/hooks/faraday.rb +16 -0
  28. data/lib/appsignal/hooks/http.rb +5 -0
  29. data/lib/appsignal/hooks/resque.rb +1 -1
  30. data/lib/appsignal/hooks/sequel.rb +32 -2
  31. data/lib/appsignal/hooks/shoryuken.rb +3 -3
  32. data/lib/appsignal/hooks/sidekiq.rb +1 -1
  33. data/lib/appsignal/integrations/action_cable.rb +5 -2
  34. data/lib/appsignal/integrations/active_support_notifications.rb +59 -19
  35. data/lib/appsignal/integrations/data_mapper.rb +14 -2
  36. data/lib/appsignal/integrations/delayed_job_plugin.rb +81 -8
  37. data/lib/appsignal/integrations/dry_monitor.rb +39 -15
  38. data/lib/appsignal/integrations/excon/appsignal_middleware.rb +21 -0
  39. data/lib/appsignal/integrations/excon.rb +52 -15
  40. data/lib/appsignal/integrations/faraday.rb +47 -12
  41. data/lib/appsignal/integrations/http.rb +43 -1
  42. data/lib/appsignal/integrations/mongo_ruby_driver.rb +73 -4
  43. data/lib/appsignal/integrations/net_http.rb +31 -2
  44. data/lib/appsignal/integrations/puma.rb +4 -1
  45. data/lib/appsignal/integrations/que.rb +256 -37
  46. data/lib/appsignal/integrations/railtie.rb +4 -1
  47. data/lib/appsignal/integrations/rake.rb +9 -3
  48. data/lib/appsignal/integrations/redis.rb +22 -1
  49. data/lib/appsignal/integrations/redis_client.rb +22 -1
  50. data/lib/appsignal/integrations/resque.rb +81 -11
  51. data/lib/appsignal/integrations/shoryuken.rb +159 -12
  52. data/lib/appsignal/integrations/sidekiq.rb +94 -16
  53. data/lib/appsignal/integrations/webmachine.rb +56 -5
  54. data/lib/appsignal/loaders/padrino.rb +2 -1
  55. data/lib/appsignal/logger/extension_backend.rb +24 -0
  56. data/lib/appsignal/logger/opentelemetry_backend.rb +66 -0
  57. data/lib/appsignal/logger.rb +13 -9
  58. data/lib/appsignal/metrics/extension_backend.rb +47 -0
  59. data/lib/appsignal/metrics/opentelemetry_backend.rb +89 -0
  60. data/lib/appsignal/opentelemetry/attributes.rb +31 -0
  61. data/lib/appsignal/opentelemetry/dependencies.rb +35 -0
  62. data/lib/appsignal/opentelemetry/error_type.rb +37 -0
  63. data/lib/appsignal/opentelemetry/http_client_request.rb +83 -0
  64. data/lib/appsignal/opentelemetry/http_method.rb +59 -0
  65. data/lib/appsignal/opentelemetry/http_response.rb +30 -0
  66. data/lib/appsignal/opentelemetry/http_server_request.rb +79 -0
  67. data/lib/appsignal/opentelemetry/messaging.rb +82 -0
  68. data/lib/appsignal/opentelemetry/proxied_exporter.rb +83 -0
  69. data/lib/appsignal/opentelemetry/rendering.rb +29 -0
  70. data/lib/appsignal/opentelemetry/sql_db_system.rb +89 -0
  71. data/lib/appsignal/opentelemetry.rb +495 -0
  72. data/lib/appsignal/rack/abstract_middleware.rb +66 -4
  73. data/lib/appsignal/rack/body_wrapper.rb +18 -5
  74. data/lib/appsignal/rack/event_handler.rb +44 -4
  75. data/lib/appsignal/rack/grape_middleware.rb +1 -0
  76. data/lib/appsignal/rack/hanami_middleware.rb +2 -1
  77. data/lib/appsignal/rack/instrumentation_middleware.rb +1 -0
  78. data/lib/appsignal/rack/rails_instrumentation.rb +1 -0
  79. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -0
  80. data/lib/appsignal/rack.rb +68 -12
  81. data/lib/appsignal/sample_data.rb +4 -0
  82. data/lib/appsignal/transaction/base_backend.rb +128 -0
  83. data/lib/appsignal/transaction/extension_backend.rb +229 -0
  84. data/lib/appsignal/transaction/opentelemetry_backend.rb +847 -0
  85. data/lib/appsignal/transaction.rb +714 -164
  86. data/lib/appsignal/utils/request_headers.rb +78 -0
  87. data/lib/appsignal/utils/stdout_and_logger_message.rb +9 -0
  88. data/lib/appsignal/utils.rb +1 -0
  89. data/lib/appsignal/version.rb +1 -1
  90. data/lib/appsignal.rb +10 -0
  91. data/sig/appsignal.rbi +630 -37
  92. data/sig/appsignal.rbs +582 -27
  93. 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(Appsignal::Transaction::HTTP_REQUEST)
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
- # Complete transaction because this is the top instrumentation middleware.
63
- Appsignal::Transaction.complete_current! unless wrapped_instrumentation
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(@instrument_event_name) do
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("close_response_body.rack") { @body.close }
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("process_response_body.rack", "Process Rack response body (#each)") do
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("process_response_body.rack", "Process Rack response body (#call)") do
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(Appsignal::Transaction::HTTP_REQUEST)
67
- transaction.start_event
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.add_params_if_nil { request.params }
122
- transaction.add_headers_if_nil { request.env }
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.add_params { params_for(request) }
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
@@ -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 = request_method_for(request)
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.add_params { params_for(request) }
126
+ transaction.add_request_payload { params_for(request) }
64
127
  transaction.add_session_data { session_data_for(request) }
65
- transaction.add_headers do
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
 
@@ -61,6 +61,10 @@ module Appsignal
61
61
  @empty
62
62
  end
63
63
 
64
+ # The sample-data category this holds (e.g. `:params`, `:request_payload`).
65
+ # Used to name the data in log messages.
66
+ attr_reader :key
67
+
64
68
  protected
65
69
 
66
70
  attr_reader :blocks
@@ -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