opentelemetry-instrumentation-rack 0.26.0 → 0.27.0

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.
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'opentelemetry/trace/status'
8
+
9
+ module OpenTelemetry
10
+ module Instrumentation
11
+ module Rack
12
+ module Middlewares
13
+ module Dup
14
+ # TracerMiddleware propagates context and instruments Rack requests
15
+ # by way of its middleware system
16
+ class TracerMiddleware
17
+ class << self
18
+ def allowed_rack_request_headers
19
+ @allowed_rack_request_headers ||= Array(config[:allowed_request_headers]).each_with_object({}) do |header, memo|
20
+ key = header.to_s.upcase.gsub(/[-\s]/, '_')
21
+ case key
22
+ when 'CONTENT_TYPE', 'CONTENT_LENGTH'
23
+ memo[key] = build_attribute_name('http.request.header.', header)
24
+ else
25
+ memo["HTTP_#{key}"] = build_attribute_name('http.request.header.', header)
26
+ end
27
+ end
28
+ end
29
+
30
+ def allowed_response_headers
31
+ @allowed_response_headers ||= Array(config[:allowed_response_headers]).each_with_object({}) do |header, memo|
32
+ memo[header] = build_attribute_name('http.response.header.', header)
33
+ memo[header.to_s.upcase] = build_attribute_name('http.response.header.', header)
34
+ end
35
+ end
36
+
37
+ def build_attribute_name(prefix, suffix)
38
+ prefix + suffix.to_s.downcase.gsub(/[-\s]/, '_')
39
+ end
40
+
41
+ def config
42
+ Rack::Instrumentation.instance.config
43
+ end
44
+
45
+ private
46
+
47
+ def clear_cached_config
48
+ @allowed_rack_request_headers = nil
49
+ @allowed_response_headers = nil
50
+ end
51
+ end
52
+
53
+ EMPTY_HASH = {}.freeze
54
+
55
+ def initialize(app)
56
+ @app = app
57
+ @untraced_endpoints = config[:untraced_endpoints]
58
+ end
59
+
60
+ def call(env)
61
+ if untraced_request?(env)
62
+ OpenTelemetry::Common::Utilities.untraced do
63
+ return @app.call(env)
64
+ end
65
+ end
66
+
67
+ original_env = env.dup
68
+ extracted_context = OpenTelemetry.propagation.extract(
69
+ env,
70
+ getter: OpenTelemetry::Common::Propagation.rack_env_getter
71
+ )
72
+ frontend_context = create_frontend_span(env, extracted_context)
73
+
74
+ # restore extracted context in this process:
75
+ OpenTelemetry::Context.with_current(frontend_context || extracted_context) do
76
+ request_span_name = create_request_span_name(env['REQUEST_URI'] || original_env['PATH_INFO'], env)
77
+ request_span_kind = frontend_context.nil? ? :server : :internal
78
+ tracer.in_span(request_span_name,
79
+ attributes: request_span_attributes(env: env),
80
+ kind: request_span_kind) do |request_span|
81
+ request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(env)
82
+ request_span.add_event('http.proxy.request.started', timestamp: request_start_time) unless request_start_time.nil?
83
+ OpenTelemetry::Instrumentation::Rack.with_span(request_span) do
84
+ @app.call(env).tap do |status, headers, response|
85
+ set_attributes_after_request(request_span, status, headers, response)
86
+ config[:response_propagators].each { |propagator| propagator.inject(headers) }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ ensure
92
+ finish_span(frontend_context)
93
+ end
94
+
95
+ private
96
+
97
+ def untraced_request?(env)
98
+ return true if @untraced_endpoints.include?(env['PATH_INFO'])
99
+ return true if config[:untraced_requests]&.call(env)
100
+
101
+ false
102
+ end
103
+
104
+ # return Context with the frontend span as the current span
105
+ def create_frontend_span(env, extracted_context)
106
+ request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(env)
107
+
108
+ return unless config[:record_frontend_span] && !request_start_time.nil?
109
+
110
+ span = tracer.start_span('http_server.proxy',
111
+ with_parent: extracted_context,
112
+ start_timestamp: request_start_time,
113
+ kind: :server)
114
+
115
+ OpenTelemetry::Trace.context_with_span(span, parent_context: extracted_context)
116
+ end
117
+
118
+ def finish_span(context)
119
+ OpenTelemetry::Trace.current_span(context).finish if context
120
+ end
121
+
122
+ def tracer
123
+ OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.tracer
124
+ end
125
+
126
+ def request_span_attributes(env:)
127
+ attributes = {
128
+ 'http.method' => env['REQUEST_METHOD'],
129
+ 'http.host' => env['HTTP_HOST'] || 'unknown',
130
+ 'server.address' => env['HTTP_HOST'] || 'unknown',
131
+ 'http.scheme' => env['rack.url_scheme'],
132
+ 'http.target' => env['QUERY_STRING'].empty? ? env['PATH_INFO'] : "#{env['PATH_INFO']}?#{env['QUERY_STRING']}",
133
+ 'http.request.method' => env['REQUEST_METHOD'],
134
+ 'url.scheme' => env['rack.url_scheme'],
135
+ 'url.path' => env['PATH_INFO']
136
+ }
137
+
138
+ attributes['url.query'] = env['QUERY_STRING'] unless env['QUERY_STRING'].empty?
139
+ if env['HTTP_USER_AGENT']
140
+ attributes['http.user_agent'] = env['HTTP_USER_AGENT']
141
+ attributes['user_agent.original'] = env['HTTP_USER_AGENT']
142
+ end
143
+ attributes.merge!(allowed_request_headers(env))
144
+ end
145
+
146
+ # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md#name
147
+ #
148
+ # recommendation: span.name(s) should be low-cardinality (e.g.,
149
+ # strip off query param value, keep param name)
150
+ #
151
+ # see http://github.com/open-telemetry/opentelemetry-specification/pull/416/files
152
+ def create_request_span_name(request_uri_or_path_info, env)
153
+ # NOTE: dd-trace-rb has implemented 'quantization' (which lowers url cardinality)
154
+ # see Datadog::Quantization::HTTP.url
155
+
156
+ if (implementation = config[:url_quantization])
157
+ implementation.call(request_uri_or_path_info, env)
158
+ else
159
+ env['REQUEST_METHOD']
160
+ end
161
+ end
162
+
163
+ def set_attributes_after_request(span, status, headers, _response)
164
+ span.status = OpenTelemetry::Trace::Status.error if (500..599).cover?(status.to_i)
165
+ span.set_attribute('http.status_code', status)
166
+ span.set_attribute('http.response.status_code', status)
167
+
168
+ # NOTE: if data is available, it would be good to do this:
169
+ # set_attribute('http.route', ...
170
+ # e.g., "/users/:userID?
171
+
172
+ allowed_response_headers(headers).each { |k, v| span.set_attribute(k, v) }
173
+ end
174
+
175
+ def allowed_request_headers(env)
176
+ return EMPTY_HASH if self.class.allowed_rack_request_headers.empty?
177
+
178
+ {}.tap do |result|
179
+ self.class.allowed_rack_request_headers.each do |key, value|
180
+ result[value] = env[key] if env.key?(key)
181
+ end
182
+ end
183
+ end
184
+
185
+ def allowed_response_headers(headers)
186
+ return EMPTY_HASH if headers.nil?
187
+ return EMPTY_HASH if self.class.allowed_response_headers.empty?
188
+
189
+ {}.tap do |result|
190
+ self.class.allowed_response_headers.each do |key, value|
191
+ if headers.key?(key)
192
+ result[value] = headers[key]
193
+ else
194
+ # do case-insensitive match:
195
+ headers.each do |k, v|
196
+ if k.upcase == key
197
+ result[value] = v
198
+ break
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ def config
207
+ Rack::Instrumentation.instance.config
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require_relative '../../util'
8
+ require 'opentelemetry/trace/status'
9
+
10
+ module OpenTelemetry
11
+ module Instrumentation
12
+ module Rack
13
+ module Middlewares
14
+ module Old
15
+ # OTel Rack Event Handler
16
+ #
17
+ # This seeds the root context for this service with the server span as the `current_span`
18
+ # allowing for callers later in the stack to reference it using {OpenTelemetry::Trace.current_span}
19
+ #
20
+ # It also registers the server span in a context dedicated to this instrumentation that users may look up
21
+ # using {OpenTelemetry::Instrumentation::Rack.current_span}, which makes it possible for users to mutate the span,
22
+ # e.g. add events or update the span name like in the {ActionPack} instrumentation.
23
+ #
24
+ # @example Rack App Using BodyProxy
25
+ # GLOBAL_LOGGER = Logger.new($stderr)
26
+ # APP_TRACER = OpenTelemetry.tracer_provider.tracer('my-app', '1.0.0')
27
+ #
28
+ # Rack::Builder.new do
29
+ # use Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::EventHandler.new]
30
+ # run lambda { |_arg|
31
+ # APP_TRACER.in_span('hello-world') do |_span|
32
+ # body = Rack::BodyProxy.new(['hello world!']) do
33
+ # rack_span = OpenTelemetry::Instrumentation::Rack.current_span
34
+ # GLOBAL_LOGGER.info("otel.trace_id=#{rack_span.context.hex_trace_id} otel.span_id=#{rack_span.context.hex_span_id}")
35
+ # end
36
+ # [200, { 'Content-Type' => 'text/plain' }, body]
37
+ # end
38
+ # }
39
+ # end
40
+ #
41
+ # @see Rack::Events
42
+ # @see OpenTelemetry::Instrumentation::Rack.current_span
43
+ class EventHandler
44
+ include ::Rack::Events::Abstract
45
+
46
+ OTEL_TOKEN_AND_SPAN = 'otel.rack.token_and_span'
47
+ EMPTY_HASH = {}.freeze
48
+
49
+ # Creates a server span for this current request using the incoming parent context
50
+ # and registers them as the {current_span}
51
+ #
52
+ # @param [Rack::Request] The current HTTP request
53
+ # @param [Rack::Response] This is nil in practice
54
+ # @return [void]
55
+ def on_start(request, _)
56
+ parent_context = if untraced_request?(request.env)
57
+ extract_remote_context(request, OpenTelemetry::Common::Utilities.untraced)
58
+ else
59
+ extract_remote_context(request)
60
+ end
61
+
62
+ span = create_span(parent_context, request)
63
+ span_ctx = OpenTelemetry::Trace.context_with_span(span, parent_context: parent_context)
64
+ rack_ctx = OpenTelemetry::Instrumentation::Rack.context_with_span(span, parent_context: span_ctx)
65
+ request.env[OTEL_TOKEN_AND_SPAN] = [OpenTelemetry::Context.attach(rack_ctx), span]
66
+ rescue StandardError => e
67
+ OpenTelemetry.handle_error(exception: e)
68
+ end
69
+
70
+ # Optionally adds debugging response headers injected from {response_propagators}
71
+ #
72
+ # @param [Rack::Request] The current HTTP request
73
+ # @param [Rack::Response] This current HTTP response
74
+ # @return [void]
75
+ def on_commit(request, response)
76
+ span = OpenTelemetry::Instrumentation::Rack.current_span
77
+ return unless span.recording?
78
+
79
+ response_propagators&.each do |propagator|
80
+ propagator.inject(response.headers)
81
+ rescue StandardError => e
82
+ OpenTelemetry.handle_error(message: 'Unable to inject response propagation headers', exception: e)
83
+ end
84
+ rescue StandardError => e
85
+ OpenTelemetry.handle_error(exception: e)
86
+ end
87
+
88
+ # Records Unexpected Exceptions on the Rack span and set the Span Status to Error
89
+ #
90
+ # @note does nothing if the span is a non-recording span
91
+ # @param [Rack::Request] The current HTTP request
92
+ # @param [Rack::Response] The current HTTP response
93
+ # @param [Exception] An unxpected error raised by the application
94
+ def on_error(request, _, error)
95
+ span = OpenTelemetry::Instrumentation::Rack.current_span
96
+ return unless span.recording?
97
+
98
+ span.record_exception(error)
99
+ span.status = OpenTelemetry::Trace::Status.error(error.class.name)
100
+ rescue StandardError => e
101
+ OpenTelemetry.handle_error(exception: e)
102
+ end
103
+
104
+ # Finishes the span making it eligible to be exported and cleans up existing contexts
105
+ #
106
+ # @note does nothing if the span is a non-recording span
107
+ # @param [Rack::Request] The current HTTP request
108
+ # @param [Rack::Response] The current HTTP response
109
+ def on_finish(request, response)
110
+ span = OpenTelemetry::Instrumentation::Rack.current_span
111
+ return unless span.recording?
112
+
113
+ add_response_attributes(span, response) if response
114
+ rescue StandardError => e
115
+ OpenTelemetry.handle_error(exception: e)
116
+ ensure
117
+ detach_context(request)
118
+ end
119
+
120
+ private
121
+
122
+ def extract_request_headers(env)
123
+ return EMPTY_HASH if allowed_request_headers.empty?
124
+
125
+ allowed_request_headers.each_with_object({}) do |(key, value), result|
126
+ result[value] = env[key] if env.key?(key)
127
+ end
128
+ end
129
+
130
+ def extract_response_attributes(response)
131
+ attributes = { 'http.status_code' => response.status.to_i }
132
+ attributes.merge!(extract_response_headers(response.headers))
133
+ attributes
134
+ end
135
+
136
+ def extract_response_headers(headers)
137
+ return EMPTY_HASH if allowed_response_headers.empty?
138
+
139
+ allowed_response_headers.each_with_object({}) do |(key, value), result|
140
+ if headers.key?(key)
141
+ result[value] = headers[key]
142
+ else
143
+ # do case-insensitive match:
144
+ headers.each do |k, v|
145
+ if k.upcase == key
146
+ result[value] = v
147
+ break
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ def untraced_request?(env)
155
+ return true if untraced_endpoints.include?(env['PATH_INFO'])
156
+ return true if untraced_requests&.call(env)
157
+
158
+ false
159
+ end
160
+
161
+ # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md#name
162
+ #
163
+ # recommendation: span.name(s) should be low-cardinality (e.g.,
164
+ # strip off query param value, keep param name)
165
+ #
166
+ # see http://github.com/open-telemetry/opentelemetry-specification/pull/416/files
167
+ def create_request_span_name(request)
168
+ # NOTE: dd-trace-rb has implemented 'quantization' (which lowers url cardinality)
169
+ # see Datadog::Quantization::HTTP.url
170
+
171
+ if (implementation = url_quantization)
172
+ request_uri_or_path_info = request.env['REQUEST_URI'] || request.path_info
173
+ implementation.call(request_uri_or_path_info, request.env)
174
+ else
175
+ "HTTP #{request.request_method}"
176
+ end
177
+ end
178
+
179
+ def extract_remote_context(request, context = Context.current)
180
+ OpenTelemetry.propagation.extract(
181
+ request.env,
182
+ context: context,
183
+ getter: OpenTelemetry::Common::Propagation.rack_env_getter
184
+ )
185
+ end
186
+
187
+ def request_span_attributes(env)
188
+ attributes = {
189
+ 'http.method' => env['REQUEST_METHOD'],
190
+ 'http.host' => env['HTTP_HOST'] || 'unknown',
191
+ 'http.scheme' => env['rack.url_scheme'],
192
+ 'http.target' => env['QUERY_STRING'].empty? ? env['PATH_INFO'] : "#{env['PATH_INFO']}?#{env['QUERY_STRING']}"
193
+ }
194
+
195
+ attributes['http.user_agent'] = env['HTTP_USER_AGENT'] if env['HTTP_USER_AGENT']
196
+ attributes.merge!(extract_request_headers(env))
197
+ attributes
198
+ end
199
+
200
+ def detach_context(request)
201
+ return nil unless request.env[OTEL_TOKEN_AND_SPAN]
202
+
203
+ token, span = request.env[OTEL_TOKEN_AND_SPAN]
204
+ span.finish
205
+ OpenTelemetry::Context.detach(token)
206
+ rescue StandardError => e
207
+ OpenTelemetry.handle_error(exception: e)
208
+ end
209
+
210
+ def add_response_attributes(span, response)
211
+ span.status = OpenTelemetry::Trace::Status.error if response.server_error?
212
+ attributes = extract_response_attributes(response)
213
+ span.add_attributes(attributes)
214
+ rescue StandardError => e
215
+ OpenTelemetry.handle_error(exception: e)
216
+ end
217
+
218
+ def record_frontend_span?
219
+ config[:record_frontend_span] == true
220
+ end
221
+
222
+ def untraced_endpoints
223
+ config[:untraced_endpoints]
224
+ end
225
+
226
+ def untraced_requests
227
+ config[:untraced_requests]
228
+ end
229
+
230
+ def url_quantization
231
+ config[:url_quantization]
232
+ end
233
+
234
+ def response_propagators
235
+ config[:response_propagators]
236
+ end
237
+
238
+ def allowed_request_headers
239
+ config[:allowed_rack_request_headers]
240
+ end
241
+
242
+ def allowed_response_headers
243
+ config[:allowed_rack_response_headers]
244
+ end
245
+
246
+ def tracer
247
+ OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.tracer
248
+ end
249
+
250
+ def config
251
+ OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.config
252
+ end
253
+
254
+ def create_span(parent_context, request)
255
+ span = tracer.start_span(
256
+ create_request_span_name(request),
257
+ with_parent: parent_context,
258
+ kind: :server,
259
+ attributes: request_span_attributes(request.env)
260
+ )
261
+ request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(request.env)
262
+ span.add_event('http.proxy.request.started', timestamp: request_start_time) unless request_start_time.nil?
263
+ span
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'opentelemetry/trace/status'
8
+
9
+ module OpenTelemetry
10
+ module Instrumentation
11
+ module Rack
12
+ module Middlewares
13
+ module Old
14
+ # TracerMiddleware propagates context and instruments Rack requests
15
+ # by way of its middleware system
16
+ class TracerMiddleware
17
+ class << self
18
+ def allowed_rack_request_headers
19
+ @allowed_rack_request_headers ||= Array(config[:allowed_request_headers]).each_with_object({}) do |header, memo|
20
+ key = header.to_s.upcase.gsub(/[-\s]/, '_')
21
+ case key
22
+ when 'CONTENT_TYPE', 'CONTENT_LENGTH'
23
+ memo[key] = build_attribute_name('http.request.header.', header)
24
+ else
25
+ memo["HTTP_#{key}"] = build_attribute_name('http.request.header.', header)
26
+ end
27
+ end
28
+ end
29
+
30
+ def allowed_response_headers
31
+ @allowed_response_headers ||= Array(config[:allowed_response_headers]).each_with_object({}) do |header, memo|
32
+ memo[header] = build_attribute_name('http.response.header.', header)
33
+ memo[header.to_s.upcase] = build_attribute_name('http.response.header.', header)
34
+ end
35
+ end
36
+
37
+ def build_attribute_name(prefix, suffix)
38
+ prefix + suffix.to_s.downcase.gsub(/[-\s]/, '_')
39
+ end
40
+
41
+ def config
42
+ Rack::Instrumentation.instance.config
43
+ end
44
+
45
+ private
46
+
47
+ def clear_cached_config
48
+ @allowed_rack_request_headers = nil
49
+ @allowed_response_headers = nil
50
+ end
51
+ end
52
+
53
+ EMPTY_HASH = {}.freeze
54
+
55
+ def initialize(app)
56
+ @app = app
57
+ @untraced_endpoints = config[:untraced_endpoints]
58
+ end
59
+
60
+ def call(env)
61
+ if untraced_request?(env)
62
+ OpenTelemetry::Common::Utilities.untraced do
63
+ return @app.call(env)
64
+ end
65
+ end
66
+
67
+ original_env = env.dup
68
+ extracted_context = OpenTelemetry.propagation.extract(
69
+ env,
70
+ getter: OpenTelemetry::Common::Propagation.rack_env_getter
71
+ )
72
+ frontend_context = create_frontend_span(env, extracted_context)
73
+
74
+ # restore extracted context in this process:
75
+ OpenTelemetry::Context.with_current(frontend_context || extracted_context) do
76
+ request_span_name = create_request_span_name(env['REQUEST_URI'] || original_env['PATH_INFO'], env)
77
+ request_span_kind = frontend_context.nil? ? :server : :internal
78
+ tracer.in_span(request_span_name,
79
+ attributes: request_span_attributes(env: env),
80
+ kind: request_span_kind) do |request_span|
81
+ request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(env)
82
+ request_span.add_event('http.proxy.request.started', timestamp: request_start_time) unless request_start_time.nil?
83
+ OpenTelemetry::Instrumentation::Rack.with_span(request_span) do
84
+ @app.call(env).tap do |status, headers, response|
85
+ set_attributes_after_request(request_span, status, headers, response)
86
+ config[:response_propagators].each { |propagator| propagator.inject(headers) }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ ensure
92
+ finish_span(frontend_context)
93
+ end
94
+
95
+ private
96
+
97
+ def untraced_request?(env)
98
+ return true if @untraced_endpoints.include?(env['PATH_INFO'])
99
+ return true if config[:untraced_requests]&.call(env)
100
+
101
+ false
102
+ end
103
+
104
+ # return Context with the frontend span as the current span
105
+ def create_frontend_span(env, extracted_context)
106
+ request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(env)
107
+
108
+ return unless config[:record_frontend_span] && !request_start_time.nil?
109
+
110
+ span = tracer.start_span('http_server.proxy',
111
+ with_parent: extracted_context,
112
+ start_timestamp: request_start_time,
113
+ kind: :server)
114
+
115
+ OpenTelemetry::Trace.context_with_span(span, parent_context: extracted_context)
116
+ end
117
+
118
+ def finish_span(context)
119
+ OpenTelemetry::Trace.current_span(context).finish if context
120
+ end
121
+
122
+ def tracer
123
+ OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.tracer
124
+ end
125
+
126
+ def request_span_attributes(env:)
127
+ attributes = {
128
+ 'http.method' => env['REQUEST_METHOD'],
129
+ 'http.host' => env['HTTP_HOST'] || 'unknown',
130
+ 'http.scheme' => env['rack.url_scheme'],
131
+ 'http.target' => env['QUERY_STRING'].empty? ? env['PATH_INFO'] : "#{env['PATH_INFO']}?#{env['QUERY_STRING']}"
132
+ }
133
+
134
+ attributes['http.user_agent'] = env['HTTP_USER_AGENT'] if env['HTTP_USER_AGENT']
135
+ attributes.merge!(allowed_request_headers(env))
136
+ end
137
+
138
+ # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md#name
139
+ #
140
+ # recommendation: span.name(s) should be low-cardinality (e.g.,
141
+ # strip off query param value, keep param name)
142
+ #
143
+ # see http://github.com/open-telemetry/opentelemetry-specification/pull/416/files
144
+ def create_request_span_name(request_uri_or_path_info, env)
145
+ # NOTE: dd-trace-rb has implemented 'quantization' (which lowers url cardinality)
146
+ # see Datadog::Quantization::HTTP.url
147
+
148
+ if (implementation = config[:url_quantization])
149
+ implementation.call(request_uri_or_path_info, env)
150
+ else
151
+ "HTTP #{env['REQUEST_METHOD']}"
152
+ end
153
+ end
154
+
155
+ def set_attributes_after_request(span, status, headers, _response)
156
+ span.status = OpenTelemetry::Trace::Status.error if (500..599).cover?(status.to_i)
157
+ span.set_attribute('http.status_code', status)
158
+
159
+ # NOTE: if data is available, it would be good to do this:
160
+ # set_attribute('http.route', ...
161
+ # e.g., "/users/:userID?
162
+
163
+ allowed_response_headers(headers).each { |k, v| span.set_attribute(k, v) }
164
+ end
165
+
166
+ def allowed_request_headers(env)
167
+ return EMPTY_HASH if self.class.allowed_rack_request_headers.empty?
168
+
169
+ {}.tap do |result|
170
+ self.class.allowed_rack_request_headers.each do |key, value|
171
+ result[value] = env[key] if env.key?(key)
172
+ end
173
+ end
174
+ end
175
+
176
+ def allowed_response_headers(headers)
177
+ return EMPTY_HASH if headers.nil?
178
+ return EMPTY_HASH if self.class.allowed_response_headers.empty?
179
+
180
+ {}.tap do |result|
181
+ self.class.allowed_response_headers.each do |key, value|
182
+ if headers.key?(key)
183
+ result[value] = headers[key]
184
+ else
185
+ # do case-insensitive match:
186
+ headers.each do |k, v|
187
+ if k.upcase == key
188
+ result[value] = v
189
+ break
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ def config
198
+ Rack::Instrumentation.instance.config
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end