opentelemetry-instrumentation-rack 0.25.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.
@@ -1,203 +0,0 @@
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
- # TracerMiddleware propagates context and instruments Rack requests
14
- # by way of its middleware system
15
- class TracerMiddleware
16
- class << self
17
- def allowed_rack_request_headers
18
- @allowed_rack_request_headers ||= Array(config[:allowed_request_headers]).each_with_object({}) do |header, memo|
19
- key = header.to_s.upcase.gsub(/[-\s]/, '_')
20
- case key
21
- when 'CONTENT_TYPE', 'CONTENT_LENGTH'
22
- memo[key] = build_attribute_name('http.request.header.', header)
23
- else
24
- memo["HTTP_#{key}"] = build_attribute_name('http.request.header.', header)
25
- end
26
- end
27
- end
28
-
29
- def allowed_response_headers
30
- @allowed_response_headers ||= Array(config[:allowed_response_headers]).each_with_object({}) do |header, memo|
31
- memo[header] = build_attribute_name('http.response.header.', header)
32
- memo[header.to_s.upcase] = build_attribute_name('http.response.header.', header)
33
- end
34
- end
35
-
36
- def build_attribute_name(prefix, suffix)
37
- prefix + suffix.to_s.downcase.gsub(/[-\s]/, '_')
38
- end
39
-
40
- def config
41
- Rack::Instrumentation.instance.config
42
- end
43
-
44
- private
45
-
46
- def clear_cached_config
47
- @allowed_rack_request_headers = nil
48
- @allowed_response_headers = nil
49
- end
50
- end
51
-
52
- EMPTY_HASH = {}.freeze
53
-
54
- def initialize(app)
55
- @app = app
56
- @untraced_endpoints = config[:untraced_endpoints]
57
- end
58
-
59
- def call(env)
60
- if untraced_request?(env)
61
- OpenTelemetry::Common::Utilities.untraced do
62
- return @app.call(env)
63
- end
64
- end
65
-
66
- original_env = env.dup
67
- extracted_context = OpenTelemetry.propagation.extract(
68
- env,
69
- getter: OpenTelemetry::Common::Propagation.rack_env_getter
70
- )
71
- frontend_context = create_frontend_span(env, extracted_context)
72
-
73
- # restore extracted context in this process:
74
- OpenTelemetry::Context.with_current(frontend_context || extracted_context) do
75
- request_span_name = create_request_span_name(env['REQUEST_URI'] || original_env['PATH_INFO'], env)
76
- request_span_kind = frontend_context.nil? ? :server : :internal
77
- tracer.in_span(request_span_name,
78
- attributes: request_span_attributes(env: env),
79
- kind: request_span_kind) do |request_span|
80
- request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(env)
81
- request_span.add_event('http.proxy.request.started', timestamp: request_start_time) unless request_start_time.nil?
82
- OpenTelemetry::Instrumentation::Rack.with_span(request_span) do
83
- @app.call(env).tap do |status, headers, response|
84
- set_attributes_after_request(request_span, status, headers, response)
85
- config[:response_propagators].each { |propagator| propagator.inject(headers) }
86
- end
87
- end
88
- end
89
- end
90
- ensure
91
- finish_span(frontend_context)
92
- end
93
-
94
- private
95
-
96
- def untraced_request?(env)
97
- return true if @untraced_endpoints.include?(env['PATH_INFO'])
98
- return true if config[:untraced_requests]&.call(env)
99
-
100
- false
101
- end
102
-
103
- # return Context with the frontend span as the current span
104
- def create_frontend_span(env, extracted_context)
105
- request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(env)
106
-
107
- return unless config[:record_frontend_span] && !request_start_time.nil?
108
-
109
- span = tracer.start_span('http_server.proxy',
110
- with_parent: extracted_context,
111
- start_timestamp: request_start_time,
112
- kind: :server)
113
-
114
- OpenTelemetry::Trace.context_with_span(span, parent_context: extracted_context)
115
- end
116
-
117
- def finish_span(context)
118
- OpenTelemetry::Trace.current_span(context).finish if context
119
- end
120
-
121
- def tracer
122
- OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.tracer
123
- end
124
-
125
- def request_span_attributes(env:)
126
- attributes = {
127
- 'http.method' => env['REQUEST_METHOD'],
128
- 'http.host' => env['HTTP_HOST'] || 'unknown',
129
- 'http.scheme' => env['rack.url_scheme'],
130
- 'http.target' => env['QUERY_STRING'].empty? ? env['PATH_INFO'] : "#{env['PATH_INFO']}?#{env['QUERY_STRING']}"
131
- }
132
-
133
- attributes['http.user_agent'] = env['HTTP_USER_AGENT'] if env['HTTP_USER_AGENT']
134
- attributes.merge!(allowed_request_headers(env))
135
- end
136
-
137
- # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md#name
138
- #
139
- # recommendation: span.name(s) should be low-cardinality (e.g.,
140
- # strip off query param value, keep param name)
141
- #
142
- # see http://github.com/open-telemetry/opentelemetry-specification/pull/416/files
143
- def create_request_span_name(request_uri_or_path_info, env)
144
- # NOTE: dd-trace-rb has implemented 'quantization' (which lowers url cardinality)
145
- # see Datadog::Quantization::HTTP.url
146
-
147
- if (implementation = config[:url_quantization])
148
- implementation.call(request_uri_or_path_info, env)
149
- else
150
- "HTTP #{env['REQUEST_METHOD']}"
151
- end
152
- end
153
-
154
- def set_attributes_after_request(span, status, headers, _response)
155
- span.status = OpenTelemetry::Trace::Status.error if (500..599).cover?(status.to_i)
156
- span.set_attribute('http.status_code', status)
157
-
158
- # NOTE: if data is available, it would be good to do this:
159
- # set_attribute('http.route', ...
160
- # e.g., "/users/:userID?
161
-
162
- allowed_response_headers(headers).each { |k, v| span.set_attribute(k, v) }
163
- end
164
-
165
- def allowed_request_headers(env)
166
- return EMPTY_HASH if self.class.allowed_rack_request_headers.empty?
167
-
168
- {}.tap do |result|
169
- self.class.allowed_rack_request_headers.each do |key, value|
170
- result[value] = env[key] if env.key?(key)
171
- end
172
- end
173
- end
174
-
175
- def allowed_response_headers(headers)
176
- return EMPTY_HASH if headers.nil?
177
- return EMPTY_HASH if self.class.allowed_response_headers.empty?
178
-
179
- {}.tap do |result|
180
- self.class.allowed_response_headers.each do |key, value|
181
- if headers.key?(key)
182
- result[value] = headers[key]
183
- else
184
- # do case-insensitive match:
185
- headers.each do |k, v|
186
- if k.upcase == key
187
- result[value] = v
188
- break
189
- end
190
- end
191
- end
192
- end
193
- end
194
- end
195
-
196
- def config
197
- Rack::Instrumentation.instance.config
198
- end
199
- end
200
- end
201
- end
202
- end
203
- end