opentelemetry-instrumentation-rack 0.26.0 → 0.27.1

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: e564fc1183315722b2f2a964482cc019b7f9667c9186dd3386582ab9f560d578
4
+ data.tar.gz: d8b4659e25076da7b89900bae8955b0ca07222a839f7f5ed3add5079d928635d
5
5
  SHA512:
6
- metadata.gz: c4d760431593691b8821997ac0f6fdec37d225485a847d37b945e2651254155a5046110c5f856ca30b172bbf2a6894cdf5e9eb4d0f3cd356eb347b759d6f8dac
7
- data.tar.gz: 1f69336f2340ecc191b490552f204f35a124643092c68ff596fb49674440c2662c1a3919ee34fb3c51cab1cafbc9e9487586d5d7d203e0f3116ca4634e638d84
6
+ metadata.gz: 331443dd4ac9af20a3df6e9fae7ad0d496947938b0b3369b3e2ca7550d19401f07ccc3c44e2fca73a0ca55c08dda9f903ccdb592af8918016e46be8f3a814a73
7
+ data.tar.gz: 26d2eab4e04f1edfe08165d9f44558a2f677030bfacc3b6f6c03f97107687c4d2693f65f55a2f1f2e96f847d9b38dcba5eccdcfe8bb4d2bb076ff9cd2a265b61
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Release History: opentelemetry-instrumentation-rack
2
2
 
3
+ ### v0.27.1 / 2025-09-16
4
+
5
+ * DOCS: Typo in Rack::Instrumentation usage example of middleware_args
6
+
7
+ ### v0.27.0 / 2025-08-19
8
+
9
+ * ADDED: Add `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable compatibility [#1594](https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/1594)
10
+
3
11
  ### v0.26.0 / 2025-01-16
4
12
 
5
13
  * BREAKING CHANGE: Set minimum supported version to Ruby 3.1
@@ -59,21 +67,21 @@
59
67
 
60
68
  ### v0.23.1 / 2023-06-05
61
69
 
62
- * FIXED: Base config options
70
+ * FIXED: Base config options
63
71
 
64
72
  ### v0.23.0 / 2023-04-17
65
73
 
66
- * BREAKING CHANGE: Remove retain_middleware_names Rack Option
67
- * BREAKING CHANGE: Drop support for EoL Ruby 2.7
74
+ * BREAKING CHANGE: Remove retain_middleware_names Rack Option
75
+ * BREAKING CHANGE: Drop support for EoL Ruby 2.7
68
76
 
69
- * ADDED: Remove retain_middleware_names Rack Option
70
- * ADDED: Drop support for EoL Ruby 2.7
71
- * ADDED: Use Rack::Events for instrumentation
77
+ * ADDED: Remove retain_middleware_names Rack Option
78
+ * ADDED: Drop support for EoL Ruby 2.7
79
+ * ADDED: Use Rack::Events for instrumentation
72
80
 
73
81
  ### v0.22.1 / 2023-01-14
74
82
 
75
- * DOCS: Fix gem homepage
76
- * DOCS: More gem documentation fixes
83
+ * DOCS: Fix gem homepage
84
+ * DOCS: More gem documentation fixes
77
85
 
78
86
  ### v0.22.0 / 2022-11-16
79
87
 
@@ -86,17 +94,17 @@
86
94
  ### v0.21.0 / 2022-06-09
87
95
 
88
96
  * Upgrading Base dependency version
89
- * FIXED: Broken test file requirements
97
+ * FIXED: Broken test file requirements
90
98
 
91
99
  ### v0.20.2 / 2022-05-02
92
100
 
93
- * FIXED: Update server instrumentation to not reflect 400 status as error
101
+ * FIXED: Update server instrumentation to not reflect 400 status as error
94
102
 
95
103
  ### v0.20.1 / 2021-12-01
96
104
 
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
105
+ * FIXED: [Instrumentation Rack] Log content type http header
106
+ * FIXED: Use monotonic clock where possible
107
+ * FIXED: Rack to stop using api env getter
100
108
 
101
109
  ### v0.20.0 / 2021-10-06
102
110
 
@@ -114,18 +122,18 @@ forwards the uri path. More details on this is available in the readme.
114
122
 
115
123
  ### v0.19.2 / 2021-08-18
116
124
 
117
- * FIXED: Rack middleware assuming script_name presence
125
+ * FIXED: Rack middleware assuming script_name presence
118
126
 
119
127
  ### v0.19.1 / 2021-08-12
120
128
 
121
- * DOCS: Update docs to rely more on environment variable configuration
129
+ * DOCS: Update docs to rely more on environment variable configuration
122
130
 
123
131
  ### v0.19.0 / 2021-06-23
124
132
 
125
- * BREAKING CHANGE: Total order constraint on span.status=
133
+ * BREAKING CHANGE: Total order constraint on span.status=
126
134
 
127
- * ADDED: Add Tracer.non_recording_span to API
128
- * FIXED: Total order constraint on span.status=
135
+ * ADDED: Add Tracer.non_recording_span to API
136
+ * FIXED: Total order constraint on span.status=
129
137
 
130
138
  ### v0.18.0 / 2021-05-21
131
139
 
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
@@ -35,23 +36,64 @@ module OpenTelemetry
35
36
  #
36
37
  # @example Default usage
37
38
  # Rack::Builder.new do
38
- # use *OpenTelemetry::Instrumentation::Rack::Instrumenation.instance.middleware_args
39
+ # use *OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args
39
40
  # run lambda { |_arg| [200, { 'Content-Type' => 'text/plain' }, body] }
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