opentelemetry-instrumentation-excon 0.23.0 → 0.24.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: a9c9b597402f3c9c890dbb5b7b534ac4d68bc9ca39617061faecf11f1a00ca60
4
- data.tar.gz: 73e820abc4c9da739c484c9229bc842eadd23d21db593c1e70be446ecae625ca
3
+ metadata.gz: 665fc138f916ed28ac04ad618f3763d486100fdfb728eb6f36afe5be44b91ea3
4
+ data.tar.gz: ab39b09837a43e659d25209960b649bd9070905f751abddd5d494e64a69b831b
5
5
  SHA512:
6
- metadata.gz: 5438e5e0158b9dff3fdf28f4dfe01395a3605e94e9f0abef6b92d6cce5ef89889f674d0921e385abff95a3d20cf33106e20ba3b10a42f1a6da2ab7a6f35dc423
7
- data.tar.gz: 37c38ec600c47a2a007cd11075cad33f36890c28f0b9c48ccd90bfaa282d7bfcdba2885792dc6f32b38aaa6f90ebbb37ebce43daf9f0a68cead00bd26bfafaf3
6
+ metadata.gz: 9aeb16c010bfee7044705d6d0a85583adfa91cb27cba4ad3cc79ba8773bd9d9edfefd8646e63d5d0724ef1c8b2051962fc606155e7f5358539cd222fdf261ba2
7
+ data.tar.gz: c497f7b505130661372b15f1c594e2084ba9a3696fba0f9f11ac132a3a9f84e8f30ce28079af653337bcdb4f18539c996f7afa9aed6a5a00822d3ecb7387fd6b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Release History: opentelemetry-instrumentation-excon
2
2
 
3
+ ### v0.24.0 / 2025-08-12
4
+
5
+ * ADDED: Add Excon `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable [#1569](https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/1569)
6
+
3
7
  ### v0.23.0 / 2025-01-16
4
8
 
5
9
  * BREAKING CHANGE: Set minimum supported version to Ruby 3.1
data/README.md CHANGED
@@ -48,3 +48,20 @@ The `opentelemetry-instrumentation-all` gem is distributed under the Apache 2.0
48
48
  [community-meetings]: https://github.com/open-telemetry/community#community-meetings
49
49
  [slack-channel]: https://cloud-native.slack.com/archives/C01NWKKMKMY
50
50
  [discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions
51
+
52
+
53
+ ## HTTP semantic convention stability
54
+
55
+ In the OpenTelemetry ecosystem, HTTP semantic conventions have now reached a stable state. However, the initial Excon instrumentation was introduced before this stability was achieved, which resulted in HTTP attributes being based on an older version of the semantic conventions.
56
+
57
+ 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.
58
+
59
+ When setting the value for `OTEL_SEMCONV_STABILITY_OPT_IN`, you can specify which conventions you wish to adopt:
60
+
61
+ - `http` - Emits the stable HTTP and networking conventions and ceases emitting the old conventions previously emitted by the instrumentation.
62
+ - `http/dup` - Emits both the old and stable HTTP and networking conventions, enabling a phased rollout of the stable semantic conventions.
63
+ - Default behavior (in the absence of either value) is to continue emitting the old HTTP and networking conventions the instrumentation previously emitted.
64
+
65
+ During the transition from old to stable conventions, Excon instrumentation code comes in three patch versions: `dup`, `old`, and `stable`. These versions are identical except for the attributes they send. Any changes to Excon instrumentation should consider all three patches.
66
+
67
+ For additional information on migration, please refer to our [documentation](https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/).
@@ -15,9 +15,10 @@ module OpenTelemetry
15
15
  include OpenTelemetry::Instrumentation::Concerns::UntracedHosts
16
16
 
17
17
  install do |_config|
18
- require_dependencies
19
- add_middleware
20
- patch
18
+ patch_type = determine_semconv
19
+ send(:"require_dependencies_#{patch_type}")
20
+ send(:"add_middleware_#{patch_type}")
21
+ send(:"patch_#{patch_type}")
21
22
  end
22
23
 
23
24
  present do
@@ -28,17 +29,56 @@ module OpenTelemetry
28
29
 
29
30
  private
30
31
 
31
- def require_dependencies
32
- require_relative 'middlewares/tracer_middleware'
33
- require_relative 'patches/socket'
32
+ def determine_semconv
33
+ stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '')
34
+ values = stability_opt_in.split(',').map(&:strip)
35
+
36
+ if values.include?('http/dup')
37
+ 'dup'
38
+ elsif values.include?('http')
39
+ 'stable'
40
+ else
41
+ 'old'
42
+ end
43
+ end
44
+
45
+ def require_dependencies_dup
46
+ require_relative 'middlewares/dup/tracer_middleware'
47
+ require_relative 'patches/dup/socket'
48
+ end
49
+
50
+ def require_dependencies_stable
51
+ require_relative 'middlewares/stable/tracer_middleware'
52
+ require_relative 'patches/stable/socket'
53
+ end
54
+
55
+ def require_dependencies_old
56
+ require_relative 'middlewares/old/tracer_middleware'
57
+ require_relative 'patches/old/socket'
58
+ end
59
+
60
+ def add_middleware_dup
61
+ ::Excon.defaults[:middlewares] = Middlewares::Dup::TracerMiddleware.around_default_stack
62
+ end
63
+
64
+ def add_middleware_stable
65
+ ::Excon.defaults[:middlewares] = Middlewares::Stable::TracerMiddleware.around_default_stack
66
+ end
67
+
68
+ def add_middleware_old
69
+ ::Excon.defaults[:middlewares] = Middlewares::Old::TracerMiddleware.around_default_stack
70
+ end
71
+
72
+ def patch_dup
73
+ ::Excon::Socket.prepend(Patches::Dup::Socket)
34
74
  end
35
75
 
36
- def add_middleware
37
- ::Excon.defaults[:middlewares] = Middlewares::TracerMiddleware.around_default_stack
76
+ def patch_stable
77
+ ::Excon::Socket.prepend(Patches::Stable::Socket)
38
78
  end
39
79
 
40
- def patch
41
- ::Excon::Socket.prepend(Patches::Socket)
80
+ def patch_old
81
+ ::Excon::Socket.prepend(Patches::Old::Socket)
42
82
  end
43
83
  end
44
84
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ module Excon
10
+ module Middlewares
11
+ module Dup
12
+ # Excon middleware for instrumentation
13
+ class TracerMiddleware < ::Excon::Middleware::Base
14
+ HTTP_METHODS_TO_UPPERCASE = %w[connect delete get head options patch post put trace].each_with_object({}) do |method, hash|
15
+ uppercase_method = method.upcase
16
+ hash[method] = uppercase_method
17
+ hash[method.to_sym] = uppercase_method
18
+ hash[uppercase_method] = uppercase_method
19
+ end.freeze
20
+
21
+ HTTP_METHODS_TO_SPAN_NAMES = HTTP_METHODS_TO_UPPERCASE.values.each_with_object({}) do |uppercase_method, hash|
22
+ hash[uppercase_method] ||= uppercase_method
23
+ end.freeze
24
+
25
+ # Constant for the HTTP status range
26
+ HTTP_STATUS_SUCCESS_RANGE = (100..399)
27
+
28
+ def request_call(datum)
29
+ return @stack.request_call(datum) if untraced?(datum)
30
+
31
+ http_method = HTTP_METHODS_TO_UPPERCASE[datum[:method]]
32
+ cleansed_url = OpenTelemetry::Common::Utilities.cleanse_url(::Excon::Utils.request_uri(datum))
33
+ attributes = {
34
+ OpenTelemetry::SemanticConventions::Trace::HTTP_HOST => datum[:host],
35
+ OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => http_method,
36
+ OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => datum[:scheme],
37
+ OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => datum[:path],
38
+ OpenTelemetry::SemanticConventions::Trace::HTTP_URL => cleansed_url,
39
+ OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => datum[:hostname],
40
+ OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => datum[:port],
41
+ 'http.request.method' => http_method,
42
+ 'url.scheme' => datum[:scheme],
43
+ 'url.path' => datum[:path],
44
+ 'url.full' => cleansed_url,
45
+ 'server.address' => datum[:hostname],
46
+ 'server.port' => datum[:port]
47
+ }
48
+ attributes['url.query'] = datum[:query] if datum[:query]
49
+ peer_service = Excon::Instrumentation.instance.config[:peer_service]
50
+ attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = peer_service if peer_service
51
+ attributes.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
52
+ span = tracer.start_span(HTTP_METHODS_TO_SPAN_NAMES[http_method], attributes: attributes, kind: :client)
53
+ ctx = OpenTelemetry::Trace.context_with_span(span)
54
+ datum[:otel_span] = span
55
+ datum[:otel_token] = OpenTelemetry::Context.attach(ctx)
56
+ OpenTelemetry.propagation.inject(datum[:headers])
57
+ @stack.request_call(datum)
58
+ end
59
+
60
+ def response_call(datum)
61
+ @stack.response_call(datum).tap do |d|
62
+ handle_response(d)
63
+ end
64
+ end
65
+
66
+ def error_call(datum)
67
+ handle_response(datum)
68
+ @stack.error_call(datum)
69
+ end
70
+
71
+ # Returns a copy of the default stack with the trace middleware injected
72
+ def self.around_default_stack
73
+ ::Excon.defaults[:middlewares].dup.tap do |default_stack|
74
+ # If the default stack contains a version of the trace middleware already...
75
+ existing_trace_middleware = default_stack.find { |m| m <= TracerMiddleware }
76
+ default_stack.delete(existing_trace_middleware) if existing_trace_middleware
77
+ # Inject after the ResponseParser middleware
78
+ response_middleware_index = default_stack.index(::Excon::Middleware::ResponseParser).to_i
79
+ default_stack.insert(response_middleware_index + 1, self)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def handle_response(datum)
86
+ datum.delete(:otel_span)&.tap do |span|
87
+ return unless span.recording?
88
+
89
+ if datum.key?(:response)
90
+ response = datum[:response]
91
+ span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, response[:status])
92
+ span.set_attribute('http.response.status_code', response[:status])
93
+ span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response[:status].to_i)
94
+ end
95
+
96
+ if datum.key?(:error)
97
+ span.status = OpenTelemetry::Trace::Status.error('Request has failed')
98
+ span.record_exception(datum[:error])
99
+ end
100
+
101
+ span.finish
102
+ OpenTelemetry::Context.detach(datum.delete(:otel_token)) if datum.include?(:otel_token)
103
+ end
104
+ rescue StandardError => e
105
+ OpenTelemetry.handle_error(e)
106
+ end
107
+
108
+ def tracer
109
+ Excon::Instrumentation.instance.tracer
110
+ end
111
+
112
+ def untraced?(datum)
113
+ datum.key?(:otel_span) || Excon::Instrumentation.instance.untraced?(datum[:host])
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ module Excon
10
+ module Middlewares
11
+ module Old
12
+ # Excon middleware for instrumentation
13
+ class TracerMiddleware < ::Excon::Middleware::Base
14
+ HTTP_METHODS_TO_UPPERCASE = %w[connect delete get head options patch post put trace].each_with_object({}) do |method, hash|
15
+ uppercase_method = method.upcase
16
+ hash[method] = uppercase_method
17
+ hash[method.to_sym] = uppercase_method
18
+ hash[uppercase_method] = uppercase_method
19
+ end.freeze
20
+
21
+ HTTP_METHODS_TO_SPAN_NAMES = HTTP_METHODS_TO_UPPERCASE.values.each_with_object({}) do |uppercase_method, hash|
22
+ hash[uppercase_method] ||= "HTTP #{uppercase_method}"
23
+ end.freeze
24
+
25
+ # Constant for the HTTP status range
26
+ HTTP_STATUS_SUCCESS_RANGE = (100..399)
27
+
28
+ def request_call(datum)
29
+ return @stack.request_call(datum) if untraced?(datum)
30
+
31
+ http_method = HTTP_METHODS_TO_UPPERCASE[datum[:method]]
32
+ attributes = {
33
+ OpenTelemetry::SemanticConventions::Trace::HTTP_HOST => datum[:host],
34
+ OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => http_method,
35
+ OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => datum[:scheme],
36
+ OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => datum[:path],
37
+ OpenTelemetry::SemanticConventions::Trace::HTTP_URL => OpenTelemetry::Common::Utilities.cleanse_url(::Excon::Utils.request_uri(datum)),
38
+ OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => datum[:hostname],
39
+ OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => datum[:port]
40
+ }
41
+ peer_service = Excon::Instrumentation.instance.config[:peer_service]
42
+ attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = peer_service if peer_service
43
+ attributes.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
44
+ span = tracer.start_span(HTTP_METHODS_TO_SPAN_NAMES[http_method], attributes: attributes, kind: :client)
45
+ ctx = OpenTelemetry::Trace.context_with_span(span)
46
+ datum[:otel_span] = span
47
+ datum[:otel_token] = OpenTelemetry::Context.attach(ctx)
48
+ OpenTelemetry.propagation.inject(datum[:headers])
49
+ @stack.request_call(datum)
50
+ end
51
+
52
+ def response_call(datum)
53
+ @stack.response_call(datum).tap do |d|
54
+ handle_response(d)
55
+ end
56
+ end
57
+
58
+ def error_call(datum)
59
+ handle_response(datum)
60
+ @stack.error_call(datum)
61
+ end
62
+
63
+ # Returns a copy of the default stack with the trace middleware injected
64
+ def self.around_default_stack
65
+ ::Excon.defaults[:middlewares].dup.tap do |default_stack|
66
+ # If the default stack contains a version of the trace middleware already...
67
+ existing_trace_middleware = default_stack.find { |m| m <= TracerMiddleware }
68
+ default_stack.delete(existing_trace_middleware) if existing_trace_middleware
69
+ # Inject after the ResponseParser middleware
70
+ response_middleware_index = default_stack.index(::Excon::Middleware::ResponseParser).to_i
71
+ default_stack.insert(response_middleware_index + 1, self)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def handle_response(datum)
78
+ datum.delete(:otel_span)&.tap do |span|
79
+ return unless span.recording?
80
+
81
+ if datum.key?(:response)
82
+ response = datum[:response]
83
+ span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, response[:status])
84
+ span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response[:status].to_i)
85
+ end
86
+
87
+ if datum.key?(:error)
88
+ span.status = OpenTelemetry::Trace::Status.error('Request has failed')
89
+ span.record_exception(datum[:error])
90
+ end
91
+
92
+ span.finish
93
+ OpenTelemetry::Context.detach(datum.delete(:otel_token)) if datum.include?(:otel_token)
94
+ end
95
+ rescue StandardError => e
96
+ OpenTelemetry.handle_error(e)
97
+ end
98
+
99
+ def tracer
100
+ Excon::Instrumentation.instance.tracer
101
+ end
102
+
103
+ def untraced?(datum)
104
+ datum.key?(:otel_span) || Excon::Instrumentation.instance.untraced?(datum[:host])
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ module Excon
10
+ module Middlewares
11
+ module Stable
12
+ # Excon middleware for instrumentation
13
+ class TracerMiddleware < ::Excon::Middleware::Base
14
+ HTTP_METHODS_TO_UPPERCASE = %w[connect delete get head options patch post put trace].each_with_object({}) do |method, hash|
15
+ uppercase_method = method.upcase
16
+ hash[method] = uppercase_method
17
+ hash[method.to_sym] = uppercase_method
18
+ hash[uppercase_method] = uppercase_method
19
+ end.freeze
20
+
21
+ HTTP_METHODS_TO_SPAN_NAMES = HTTP_METHODS_TO_UPPERCASE.values.each_with_object({}) do |uppercase_method, hash|
22
+ hash[uppercase_method] ||= uppercase_method
23
+ end.freeze
24
+
25
+ # Constant for the HTTP status range
26
+ HTTP_STATUS_SUCCESS_RANGE = (100..399)
27
+
28
+ def request_call(datum)
29
+ return @stack.request_call(datum) if untraced?(datum)
30
+
31
+ http_method = HTTP_METHODS_TO_UPPERCASE[datum[:method]]
32
+ attributes = {
33
+ 'http.request.method' => http_method,
34
+ 'url.scheme' => datum[:scheme],
35
+ 'url.path' => datum[:path],
36
+ 'url.full' => OpenTelemetry::Common::Utilities.cleanse_url(::Excon::Utils.request_uri(datum)),
37
+ 'server.address' => datum[:hostname],
38
+ 'server.port' => datum[:port]
39
+ }
40
+ attributes['url.query'] = datum[:query] if datum[:query]
41
+ peer_service = Excon::Instrumentation.instance.config[:peer_service]
42
+ attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = peer_service if peer_service
43
+ attributes.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
44
+ span = tracer.start_span(HTTP_METHODS_TO_SPAN_NAMES[http_method], attributes: attributes, kind: :client)
45
+ ctx = OpenTelemetry::Trace.context_with_span(span)
46
+ datum[:otel_span] = span
47
+ datum[:otel_token] = OpenTelemetry::Context.attach(ctx)
48
+ OpenTelemetry.propagation.inject(datum[:headers])
49
+ @stack.request_call(datum)
50
+ end
51
+
52
+ def response_call(datum)
53
+ @stack.response_call(datum).tap do |d|
54
+ handle_response(d)
55
+ end
56
+ end
57
+
58
+ def error_call(datum)
59
+ handle_response(datum)
60
+ @stack.error_call(datum)
61
+ end
62
+
63
+ # Returns a copy of the default stack with the trace middleware injected
64
+ def self.around_default_stack
65
+ ::Excon.defaults[:middlewares].dup.tap do |default_stack|
66
+ # If the default stack contains a version of the trace middleware already...
67
+ existing_trace_middleware = default_stack.find { |m| m <= TracerMiddleware }
68
+ default_stack.delete(existing_trace_middleware) if existing_trace_middleware
69
+ # Inject after the ResponseParser middleware
70
+ response_middleware_index = default_stack.index(::Excon::Middleware::ResponseParser).to_i
71
+ default_stack.insert(response_middleware_index + 1, self)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def handle_response(datum)
78
+ datum.delete(:otel_span)&.tap do |span|
79
+ return unless span.recording?
80
+
81
+ if datum.key?(:response)
82
+ response = datum[:response]
83
+ span.set_attribute('http.response.status_code', response[:status])
84
+ span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response[:status].to_i)
85
+ end
86
+
87
+ if datum.key?(:error)
88
+ span.status = OpenTelemetry::Trace::Status.error('Request has failed')
89
+ span.record_exception(datum[:error])
90
+ end
91
+
92
+ span.finish
93
+ OpenTelemetry::Context.detach(datum.delete(:otel_token)) if datum.include?(:otel_token)
94
+ end
95
+ rescue StandardError => e
96
+ OpenTelemetry.handle_error(e)
97
+ end
98
+
99
+ def tracer
100
+ Excon::Instrumentation.instance.tracer
101
+ end
102
+
103
+ def untraced?(datum)
104
+ datum.key?(:otel_span) || Excon::Instrumentation.instance.untraced?(datum[:host])
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ module Excon
10
+ module Patches
11
+ module Dup
12
+ # Module to prepend to an Excon Socket for instrumentation
13
+ module Socket
14
+ private
15
+
16
+ def connect
17
+ return super if untraced?
18
+
19
+ if @data[:proxy]
20
+ conn_address = @data.dig(:proxy, :hostname)
21
+ conn_port = @data.dig(:proxy, :port)
22
+ else
23
+ conn_address = @data[:hostname]
24
+ conn_port = @port
25
+ end
26
+
27
+ attributes = {
28
+ OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => conn_address,
29
+ OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => conn_port,
30
+ 'server.address' => conn_address,
31
+ 'server.port' => conn_port
32
+ }.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
33
+
34
+ if is_a?(::Excon::SSLSocket) && @data[:proxy]
35
+ span_name = 'CONNECT'
36
+ span_kind = :client
37
+ else
38
+ span_name = 'connect'
39
+ span_kind = :internal
40
+ end
41
+
42
+ tracer.in_span(span_name, attributes: attributes, kind: span_kind) do
43
+ super
44
+ end
45
+ end
46
+
47
+ def tracer
48
+ Excon::Instrumentation.instance.tracer
49
+ end
50
+
51
+ def untraced?
52
+ address = if @data[:proxy]
53
+ @data.dig(:proxy, :hostname)
54
+ else
55
+ @data[:hostname]
56
+ end
57
+
58
+ Excon::Instrumentation.instance.untraced?(address)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ module Excon
10
+ module Patches
11
+ module Old
12
+ # Module to prepend to an Excon Socket for instrumentation
13
+ module Socket
14
+ private
15
+
16
+ def connect
17
+ return super if untraced?
18
+
19
+ if @data[:proxy]
20
+ conn_address = @data.dig(:proxy, :hostname)
21
+ conn_port = @data.dig(:proxy, :port)
22
+ else
23
+ conn_address = @data[:hostname]
24
+ conn_port = @port
25
+ end
26
+
27
+ attributes = { OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => conn_address, OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => conn_port }.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
28
+
29
+ if is_a?(::Excon::SSLSocket) && @data[:proxy]
30
+ span_name = 'HTTP CONNECT'
31
+ span_kind = :client
32
+ else
33
+ span_name = 'connect'
34
+ span_kind = :internal
35
+ end
36
+
37
+ tracer.in_span(span_name, attributes: attributes, kind: span_kind) do
38
+ super
39
+ end
40
+ end
41
+
42
+ def tracer
43
+ Excon::Instrumentation.instance.tracer
44
+ end
45
+
46
+ def untraced?
47
+ address = if @data[:proxy]
48
+ @data.dig(:proxy, :hostname)
49
+ else
50
+ @data[:hostname]
51
+ end
52
+
53
+ Excon::Instrumentation.instance.untraced?(address)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ module Excon
10
+ module Patches
11
+ module Stable
12
+ # Module to prepend to an Excon Socket for instrumentation
13
+ module Socket
14
+ private
15
+
16
+ def connect
17
+ return super if untraced?
18
+
19
+ if @data[:proxy]
20
+ conn_address = @data.dig(:proxy, :hostname)
21
+ conn_port = @data.dig(:proxy, :port)
22
+ else
23
+ conn_address = @data[:hostname]
24
+ conn_port = @port
25
+ end
26
+
27
+ attributes = { 'server.address' => conn_address, 'server.port' => conn_port }.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
28
+
29
+ if is_a?(::Excon::SSLSocket) && @data[:proxy]
30
+ span_name = 'CONNECT'
31
+ span_kind = :client
32
+ else
33
+ span_name = 'connect'
34
+ span_kind = :internal
35
+ end
36
+
37
+ tracer.in_span(span_name, attributes: attributes, kind: span_kind) do
38
+ super
39
+ end
40
+ end
41
+
42
+ def tracer
43
+ Excon::Instrumentation.instance.tracer
44
+ end
45
+
46
+ def untraced?
47
+ address = if @data[:proxy]
48
+ @data.dig(:proxy, :hostname)
49
+ else
50
+ @data[:hostname]
51
+ end
52
+
53
+ Excon::Instrumentation.instance.untraced?(address)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -7,7 +7,7 @@
7
7
  module OpenTelemetry
8
8
  module Instrumentation
9
9
  module Excon
10
- VERSION = '0.23.0'
10
+ VERSION = '0.24.0'
11
11
  end
12
12
  end
13
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-instrumentation-excon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.0
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTelemetry Authors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-16 00:00:00.000000000 Z
11
+ date: 2025-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opentelemetry-api
@@ -38,146 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.23.0
41
- - !ruby/object:Gem::Dependency
42
- name: appraisal
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '2.5'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '2.5'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '2.4'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '2.4'
69
- - !ruby/object:Gem::Dependency
70
- name: minitest
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '5.0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '5.0'
83
- - !ruby/object:Gem::Dependency
84
- name: opentelemetry-sdk
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.1'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.1'
97
- - !ruby/object:Gem::Dependency
98
- name: opentelemetry-test-helpers
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '0.3'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '0.3'
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: 1.69.1
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: 1.69.1
125
- - !ruby/object:Gem::Dependency
126
- name: rubocop-performance
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: 1.23.0
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: 1.23.0
139
- - !ruby/object:Gem::Dependency
140
- name: simplecov
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: 0.17.1
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: 0.17.1
153
- - !ruby/object:Gem::Dependency
154
- name: webmock
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: 3.24.0
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: 3.24.0
167
- - !ruby/object:Gem::Dependency
168
- name: yard
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '0.9'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '0.9'
181
41
  description: Excon instrumentation for the OpenTelemetry framework
182
42
  email:
183
43
  - cncf-opentelemetry-contributors@lists.cncf.io
@@ -194,17 +54,21 @@ files:
194
54
  - lib/opentelemetry/instrumentation/concerns/untraced_hosts.rb
195
55
  - lib/opentelemetry/instrumentation/excon.rb
196
56
  - lib/opentelemetry/instrumentation/excon/instrumentation.rb
197
- - lib/opentelemetry/instrumentation/excon/middlewares/tracer_middleware.rb
198
- - lib/opentelemetry/instrumentation/excon/patches/socket.rb
57
+ - lib/opentelemetry/instrumentation/excon/middlewares/dup/tracer_middleware.rb
58
+ - lib/opentelemetry/instrumentation/excon/middlewares/old/tracer_middleware.rb
59
+ - lib/opentelemetry/instrumentation/excon/middlewares/stable/tracer_middleware.rb
60
+ - lib/opentelemetry/instrumentation/excon/patches/dup/socket.rb
61
+ - lib/opentelemetry/instrumentation/excon/patches/old/socket.rb
62
+ - lib/opentelemetry/instrumentation/excon/patches/stable/socket.rb
199
63
  - lib/opentelemetry/instrumentation/excon/version.rb
200
64
  homepage: https://github.com/open-telemetry/opentelemetry-ruby-contrib
201
65
  licenses:
202
66
  - Apache-2.0
203
67
  metadata:
204
- changelog_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-excon/0.23.0/file/CHANGELOG.md
68
+ changelog_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-excon/0.24.0/file/CHANGELOG.md
205
69
  source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/excon
206
70
  bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues
207
- documentation_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-excon/0.23.0
71
+ documentation_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-excon/0.24.0
208
72
  post_install_message:
209
73
  rdoc_options: []
210
74
  require_paths:
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright The OpenTelemetry Authors
4
- #
5
- # SPDX-License-Identifier: Apache-2.0
6
-
7
- module OpenTelemetry
8
- module Instrumentation
9
- module Excon
10
- module Middlewares
11
- # Excon middleware for instrumentation
12
- class TracerMiddleware < ::Excon::Middleware::Base
13
- HTTP_METHODS_TO_UPPERCASE = %w[connect delete get head options patch post put trace].each_with_object({}) do |method, hash|
14
- uppercase_method = method.upcase
15
- hash[method] = uppercase_method
16
- hash[method.to_sym] = uppercase_method
17
- hash[uppercase_method] = uppercase_method
18
- end.freeze
19
-
20
- HTTP_METHODS_TO_SPAN_NAMES = HTTP_METHODS_TO_UPPERCASE.values.each_with_object({}) do |uppercase_method, hash|
21
- hash[uppercase_method] ||= "HTTP #{uppercase_method}"
22
- end.freeze
23
-
24
- # Constant for the HTTP status range
25
- HTTP_STATUS_SUCCESS_RANGE = (100..399)
26
-
27
- def request_call(datum)
28
- return @stack.request_call(datum) if untraced?(datum)
29
-
30
- http_method = HTTP_METHODS_TO_UPPERCASE[datum[:method]]
31
- attributes = {
32
- OpenTelemetry::SemanticConventions::Trace::HTTP_HOST => datum[:host],
33
- OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => http_method,
34
- OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => datum[:scheme],
35
- OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => datum[:path],
36
- OpenTelemetry::SemanticConventions::Trace::HTTP_URL => OpenTelemetry::Common::Utilities.cleanse_url(::Excon::Utils.request_uri(datum)),
37
- OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => datum[:hostname],
38
- OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => datum[:port]
39
- }
40
- peer_service = Excon::Instrumentation.instance.config[:peer_service]
41
- attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = peer_service if peer_service
42
- attributes.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
43
- span = tracer.start_span(HTTP_METHODS_TO_SPAN_NAMES[http_method], attributes: attributes, kind: :client)
44
- ctx = OpenTelemetry::Trace.context_with_span(span)
45
- datum[:otel_span] = span
46
- datum[:otel_token] = OpenTelemetry::Context.attach(ctx)
47
- OpenTelemetry.propagation.inject(datum[:headers])
48
- @stack.request_call(datum)
49
- end
50
-
51
- def response_call(datum)
52
- @stack.response_call(datum).tap do |d|
53
- handle_response(d)
54
- end
55
- end
56
-
57
- def error_call(datum)
58
- handle_response(datum)
59
- @stack.error_call(datum)
60
- end
61
-
62
- # Returns a copy of the default stack with the trace middleware injected
63
- def self.around_default_stack
64
- ::Excon.defaults[:middlewares].dup.tap do |default_stack|
65
- # If the default stack contains a version of the trace middleware already...
66
- existing_trace_middleware = default_stack.find { |m| m <= TracerMiddleware }
67
- default_stack.delete(existing_trace_middleware) if existing_trace_middleware
68
- # Inject after the ResponseParser middleware
69
- response_middleware_index = default_stack.index(::Excon::Middleware::ResponseParser).to_i
70
- default_stack.insert(response_middleware_index + 1, self)
71
- end
72
- end
73
-
74
- private
75
-
76
- def handle_response(datum)
77
- datum.delete(:otel_span)&.tap do |span|
78
- return unless span.recording?
79
-
80
- if datum.key?(:response)
81
- response = datum[:response]
82
- span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, response[:status])
83
- span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response[:status].to_i)
84
- end
85
-
86
- if datum.key?(:error)
87
- span.status = OpenTelemetry::Trace::Status.error('Request has failed')
88
- span.record_exception(datum[:error])
89
- end
90
-
91
- span.finish
92
- OpenTelemetry::Context.detach(datum.delete(:otel_token)) if datum.include?(:otel_token)
93
- end
94
- rescue StandardError => e
95
- OpenTelemetry.handle_error(e)
96
- end
97
-
98
- def tracer
99
- Excon::Instrumentation.instance.tracer
100
- end
101
-
102
- def untraced?(datum)
103
- datum.key?(:otel_span) || Excon::Instrumentation.instance.untraced?(datum[:host])
104
- end
105
- end
106
- end
107
- end
108
- end
109
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright The OpenTelemetry Authors
4
- #
5
- # SPDX-License-Identifier: Apache-2.0
6
-
7
- module OpenTelemetry
8
- module Instrumentation
9
- module Excon
10
- module Patches
11
- # Module to prepend to an Excon Socket for instrumentation
12
- module Socket
13
- private
14
-
15
- def connect
16
- return super if untraced?
17
-
18
- if @data[:proxy]
19
- conn_address = @data.dig(:proxy, :hostname)
20
- conn_port = @data.dig(:proxy, :port)
21
- else
22
- conn_address = @data[:hostname]
23
- conn_port = @port
24
- end
25
-
26
- attributes = { OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => conn_address, OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => conn_port }.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
27
-
28
- if is_a?(::Excon::SSLSocket) && @data[:proxy]
29
- span_name = 'HTTP CONNECT'
30
- span_kind = :client
31
- else
32
- span_name = 'connect'
33
- span_kind = :internal
34
- end
35
-
36
- tracer.in_span(span_name, attributes: attributes, kind: span_kind) do
37
- super
38
- end
39
- end
40
-
41
- def tracer
42
- Excon::Instrumentation.instance.tracer
43
- end
44
-
45
- def untraced?
46
- address = if @data[:proxy]
47
- @data.dig(:proxy, :hostname)
48
- else
49
- @data[:hostname]
50
- end
51
-
52
- Excon::Instrumentation.instance.untraced?(address)
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end