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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac289c86c4ea8754cb2d6fd9cd54939a18749879c548a7a0b268d03f0c1655fb
4
- data.tar.gz: c7f576b24aa2d5ba3481e42c1b85761475d27b141d8302c9739fbca326b128cc
3
+ metadata.gz: fc9a992d78dc638729e4aedee6f1ad68e8b78cb821e87f40a0e6cbd36f0fe342
4
+ data.tar.gz: a9dc3e570b412ece3e0aa7639ccd5ad4fc492eb684cfa22637381a9db2e6b637
5
5
  SHA512:
6
- metadata.gz: c4d760431593691b8821997ac0f6fdec37d225485a847d37b945e2651254155a5046110c5f856ca30b172bbf2a6894cdf5e9eb4d0f3cd356eb347b759d6f8dac
7
- data.tar.gz: 1f69336f2340ecc191b490552f204f35a124643092c68ff596fb49674440c2662c1a3919ee34fb3c51cab1cafbc9e9487586d5d7d203e0f3116ca4634e638d84
6
+ metadata.gz: 93eb31f83a0397672772a83029e78c191297c2c1ee40a0714350266322e27d9d1fb2bb4ba5b16db5f3510eb26f0fadbb66e6c9fa813b2eeb1cdda09d7cf63647
7
+ data.tar.gz: fadb07f473254bf14c9e3a85e3a792ad7c37e89d194562bcebd8a9179809f873da1efc1a4b4064e3a9c5a5db17b834a5db8df3ac5b2150fe1f5b3d5541e17327
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Release History: opentelemetry-instrumentation-rack
2
2
 
3
+ ### v0.27.0 / 2025-08-19
4
+
5
+ * ADDED: Add `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable compatibility [#1594](https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/1594)
6
+
3
7
  ### v0.26.0 / 2025-01-16
4
8
 
5
9
  * BREAKING CHANGE: Set minimum supported version to Ruby 3.1
@@ -59,21 +63,21 @@
59
63
 
60
64
  ### v0.23.1 / 2023-06-05
61
65
 
62
- * FIXED: Base config options
66
+ * FIXED: Base config options
63
67
 
64
68
  ### v0.23.0 / 2023-04-17
65
69
 
66
- * BREAKING CHANGE: Remove retain_middleware_names Rack Option
67
- * BREAKING CHANGE: Drop support for EoL Ruby 2.7
70
+ * BREAKING CHANGE: Remove retain_middleware_names Rack Option
71
+ * BREAKING CHANGE: Drop support for EoL Ruby 2.7
68
72
 
69
- * ADDED: Remove retain_middleware_names Rack Option
70
- * ADDED: Drop support for EoL Ruby 2.7
71
- * ADDED: Use Rack::Events for instrumentation
73
+ * ADDED: Remove retain_middleware_names Rack Option
74
+ * ADDED: Drop support for EoL Ruby 2.7
75
+ * ADDED: Use Rack::Events for instrumentation
72
76
 
73
77
  ### v0.22.1 / 2023-01-14
74
78
 
75
- * DOCS: Fix gem homepage
76
- * DOCS: More gem documentation fixes
79
+ * DOCS: Fix gem homepage
80
+ * DOCS: More gem documentation fixes
77
81
 
78
82
  ### v0.22.0 / 2022-11-16
79
83
 
@@ -86,17 +90,17 @@
86
90
  ### v0.21.0 / 2022-06-09
87
91
 
88
92
  * Upgrading Base dependency version
89
- * FIXED: Broken test file requirements
93
+ * FIXED: Broken test file requirements
90
94
 
91
95
  ### v0.20.2 / 2022-05-02
92
96
 
93
- * FIXED: Update server instrumentation to not reflect 400 status as error
97
+ * FIXED: Update server instrumentation to not reflect 400 status as error
94
98
 
95
99
  ### v0.20.1 / 2021-12-01
96
100
 
97
- * FIXED: [Instrumentation Rack] Log content type http header
98
- * FIXED: Use monotonic clock where possible
99
- * FIXED: Rack to stop using api env getter
101
+ * FIXED: [Instrumentation Rack] Log content type http header
102
+ * FIXED: Use monotonic clock where possible
103
+ * FIXED: Rack to stop using api env getter
100
104
 
101
105
  ### v0.20.0 / 2021-10-06
102
106
 
@@ -114,18 +118,18 @@ forwards the uri path. More details on this is available in the readme.
114
118
 
115
119
  ### v0.19.2 / 2021-08-18
116
120
 
117
- * FIXED: Rack middleware assuming script_name presence
121
+ * FIXED: Rack middleware assuming script_name presence
118
122
 
119
123
  ### v0.19.1 / 2021-08-12
120
124
 
121
- * DOCS: Update docs to rely more on environment variable configuration
125
+ * DOCS: Update docs to rely more on environment variable configuration
122
126
 
123
127
  ### v0.19.0 / 2021-06-23
124
128
 
125
- * BREAKING CHANGE: Total order constraint on span.status=
129
+ * BREAKING CHANGE: Total order constraint on span.status=
126
130
 
127
- * ADDED: Add Tracer.non_recording_span to API
128
- * FIXED: Total order constraint on span.status=
131
+ * ADDED: Add Tracer.non_recording_span to API
132
+ * FIXED: Total order constraint on span.status=
129
133
 
130
134
  ### v0.18.0 / 2021-05-21
131
135
 
data/README.md CHANGED
@@ -81,7 +81,7 @@ end
81
81
 
82
82
  ## Examples
83
83
 
84
- Example usage can be seen in the `./example/trace_demonstration.rb` file [here](https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/instrumentation/rack/example/trace_demonstration.rb)
84
+ Example usage can be seen in the [`./example/trace_demonstration.rb` file](https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/instrumentation/rack/example/trace_demonstration.rb)
85
85
 
86
86
  ## How can I get involved?
87
87
 
@@ -101,3 +101,19 @@ The `opentelemetry-instrumentation-rack` gem is distributed under the Apache 2.0
101
101
  [community-meetings]: https://github.com/open-telemetry/community#community-meetings
102
102
  [slack-channel]: https://cloud-native.slack.com/archives/C01NWKKMKMY
103
103
  [discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions
104
+
105
+ ## HTTP semantic convention stability
106
+
107
+ In the OpenTelemetry ecosystem, HTTP semantic conventions have now reached a stable state. However, the initial Rack instrumentation was introduced before this stability was achieved, which resulted in HTTP attributes being based on an older version of the semantic conventions.
108
+
109
+ To facilitate the migration to stable semantic conventions, you can use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. This variable allows you to opt-in to the new stable conventions, ensuring compatibility and future-proofing your instrumentation.
110
+
111
+ When setting the value for `OTEL_SEMCONV_STABILITY_OPT_IN`, you can specify which conventions you wish to adopt:
112
+
113
+ - `http` - Emits the stable HTTP and networking conventions and ceases emitting the old conventions previously emitted by the instrumentation.
114
+ - `http/dup` - Emits both the old and stable HTTP and networking conventions, enabling a phased rollout of the stable semantic conventions.
115
+ - Default behavior (in the absence of either value) is to continue emitting the old HTTP and networking conventions the instrumentation previously emitted.
116
+
117
+ During the transition from old to stable conventions, Rack instrumentation code comes in three patch versions: `dup`, `old`, and `stable`. These versions are identical except for the attributes they send. Any changes to Rack instrumentation should consider all three patches.
118
+
119
+ For additional information on migration, please refer to our [documentation](https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/).
@@ -13,7 +13,8 @@ module OpenTelemetry
13
13
  # instrumentation
14
14
  class Instrumentation < OpenTelemetry::Instrumentation::Base
15
15
  install do |_config|
16
- require_dependencies
16
+ patch_type = determine_semconv
17
+ send(:"require_dependencies_#{patch_type}")
17
18
  end
18
19
 
19
20
  present do
@@ -40,18 +41,59 @@ module OpenTelemetry
40
41
  # end
41
42
  # @return [Array] consisting of a middleware and arguments used in rack builders
42
43
  def middleware_args
43
- if config.fetch(:use_rack_events, false) == true && defined?(OpenTelemetry::Instrumentation::Rack::Middlewares::EventHandler)
44
- [::Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::EventHandler.new]]
44
+ if config.fetch(:use_rack_events, false) == true && defined?(OpenTelemetry::Instrumentation::Rack::Middlewares::Old::EventHandler)
45
+ [::Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::Old::EventHandler.new]]
45
46
  else
46
- [OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware]
47
+ [OpenTelemetry::Instrumentation::Rack::Middlewares::Old::TracerMiddleware]
48
+ end
49
+ end
50
+
51
+ alias middleware_args_old middleware_args
52
+
53
+ def middleware_args_dup
54
+ if config.fetch(:use_rack_events, false) == true && defined?(OpenTelemetry::Instrumentation::Rack::Middlewares::Dup::EventHandler)
55
+ [::Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::Dup::EventHandler.new]]
56
+ else
57
+ [OpenTelemetry::Instrumentation::Rack::Middlewares::Dup::TracerMiddleware]
58
+ end
59
+ end
60
+
61
+ def middleware_args_stable
62
+ if config.fetch(:use_rack_events, false) == true && defined?(OpenTelemetry::Instrumentation::Rack::Middlewares::Stable::EventHandler)
63
+ [::Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::Stable::EventHandler.new]]
64
+ else
65
+ [OpenTelemetry::Instrumentation::Rack::Middlewares::Stable::TracerMiddleware]
47
66
  end
48
67
  end
49
68
 
50
69
  private
51
70
 
52
- def require_dependencies
53
- require_relative 'middlewares/event_handler' if defined?(::Rack::Events)
54
- require_relative 'middlewares/tracer_middleware'
71
+ def determine_semconv
72
+ stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '')
73
+ values = stability_opt_in.split(',').map(&:strip)
74
+
75
+ if values.include?('http/dup')
76
+ 'dup'
77
+ elsif values.include?('http')
78
+ 'stable'
79
+ else
80
+ 'old'
81
+ end
82
+ end
83
+
84
+ def require_dependencies_old
85
+ require_relative 'middlewares/old/event_handler' if defined?(::Rack::Events)
86
+ require_relative 'middlewares/old/tracer_middleware'
87
+ end
88
+
89
+ def require_dependencies_stable
90
+ require_relative 'middlewares/stable/event_handler' if defined?(::Rack::Events)
91
+ require_relative 'middlewares/stable/tracer_middleware'
92
+ end
93
+
94
+ def require_dependencies_dup
95
+ require_relative 'middlewares/dup/event_handler' if defined?(::Rack::Events)
96
+ require_relative 'middlewares/dup/tracer_middleware'
55
97
  end
56
98
 
57
99
  def config_options(user_config)
@@ -0,0 +1,281 @@
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 Dup
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 = {
132
+ 'http.status_code' => response.status.to_i,
133
+ 'http.response.status_code' => response.status.to_i
134
+ }
135
+ attributes.merge!(extract_response_headers(response.headers))
136
+ attributes
137
+ end
138
+
139
+ def extract_response_headers(headers)
140
+ return EMPTY_HASH if allowed_response_headers.empty?
141
+
142
+ allowed_response_headers.each_with_object({}) do |(key, value), result|
143
+ if headers.key?(key)
144
+ result[value] = headers[key]
145
+ else
146
+ # do case-insensitive match:
147
+ headers.each do |k, v|
148
+ if k.upcase == key
149
+ result[value] = v
150
+ break
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ def untraced_request?(env)
158
+ return true if untraced_endpoints.include?(env['PATH_INFO'])
159
+ return true if untraced_requests&.call(env)
160
+
161
+ false
162
+ end
163
+
164
+ # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md#name
165
+ #
166
+ # recommendation: span.name(s) should be low-cardinality (e.g.,
167
+ # strip off query param value, keep param name)
168
+ #
169
+ # see http://github.com/open-telemetry/opentelemetry-specification/pull/416/files
170
+ def create_request_span_name(request)
171
+ # NOTE: dd-trace-rb has implemented 'quantization' (which lowers url cardinality)
172
+ # see Datadog::Quantization::HTTP.url
173
+
174
+ if (implementation = url_quantization)
175
+ request_uri_or_path_info = request.env['REQUEST_URI'] || request.path_info
176
+ implementation.call(request_uri_or_path_info, request.env)
177
+ else
178
+ request.request_method.to_s
179
+ end
180
+ end
181
+
182
+ def extract_remote_context(request, context = Context.current)
183
+ OpenTelemetry.propagation.extract(
184
+ request.env,
185
+ context: context,
186
+ getter: OpenTelemetry::Common::Propagation.rack_env_getter
187
+ )
188
+ end
189
+
190
+ def request_span_attributes(env)
191
+ attributes = {
192
+ 'http.method' => env['REQUEST_METHOD'],
193
+ 'http.host' => env['HTTP_HOST'] || 'unknown',
194
+ 'server.address' => env['HTTP_HOST'] || 'unknown',
195
+ 'http.scheme' => env['rack.url_scheme'],
196
+ 'http.target' => env['QUERY_STRING'].empty? ? env['PATH_INFO'] : "#{env['PATH_INFO']}?#{env['QUERY_STRING']}",
197
+ 'http.request.method' => env['REQUEST_METHOD'],
198
+ 'url.scheme' => env['rack.url_scheme'],
199
+ 'url.path' => env['PATH_INFO']
200
+ }
201
+
202
+ attributes['url.query'] = env['QUERY_STRING'] unless env['QUERY_STRING'].empty?
203
+ if env['HTTP_USER_AGENT']
204
+ attributes['http.user_agent'] = env['HTTP_USER_AGENT']
205
+ attributes['user_agent.original'] = env['HTTP_USER_AGENT']
206
+ end
207
+ attributes.merge!(extract_request_headers(env))
208
+ attributes
209
+ end
210
+
211
+ def detach_context(request)
212
+ return nil unless request.env[OTEL_TOKEN_AND_SPAN]
213
+
214
+ token, span = request.env[OTEL_TOKEN_AND_SPAN]
215
+ span.finish
216
+ OpenTelemetry::Context.detach(token)
217
+ rescue StandardError => e
218
+ OpenTelemetry.handle_error(exception: e)
219
+ end
220
+
221
+ def add_response_attributes(span, response)
222
+ span.status = OpenTelemetry::Trace::Status.error if response.server_error?
223
+ attributes = extract_response_attributes(response)
224
+ span.add_attributes(attributes)
225
+ rescue StandardError => e
226
+ OpenTelemetry.handle_error(exception: e)
227
+ end
228
+
229
+ def record_frontend_span?
230
+ config[:record_frontend_span] == true
231
+ end
232
+
233
+ def untraced_endpoints
234
+ config[:untraced_endpoints]
235
+ end
236
+
237
+ def untraced_requests
238
+ config[:untraced_requests]
239
+ end
240
+
241
+ def url_quantization
242
+ config[:url_quantization]
243
+ end
244
+
245
+ def response_propagators
246
+ config[:response_propagators]
247
+ end
248
+
249
+ def allowed_request_headers
250
+ config[:allowed_rack_request_headers]
251
+ end
252
+
253
+ def allowed_response_headers
254
+ config[:allowed_rack_response_headers]
255
+ end
256
+
257
+ def tracer
258
+ OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.tracer
259
+ end
260
+
261
+ def config
262
+ OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.config
263
+ end
264
+
265
+ def create_span(parent_context, request)
266
+ span = tracer.start_span(
267
+ create_request_span_name(request),
268
+ with_parent: parent_context,
269
+ kind: :server,
270
+ attributes: request_span_attributes(request.env)
271
+ )
272
+ request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(request.env)
273
+ span.add_event('http.proxy.request.started', timestamp: request_start_time) unless request_start_time.nil?
274
+ span
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end