Skip to content

Commit 774496f

Browse files
feat: Ethon HTTP semantic convention stability migration (#1561)
* feat: HTTP semantic convention stability migration * fix: udpate span name * fix: update span names when no method is avaliable --------- Co-authored-by: Kayla Reopelle <[email protected]>
1 parent 1d3086c commit 774496f

File tree

10 files changed

+1076
-140
lines changed

10 files changed

+1076
-140
lines changed

instrumentation/ethon/Appraisals

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
# frozen_string_literal: true
22

3-
appraise 'ethon-0.16' do
4-
gem 'ethon', '~> 0.16.0'
5-
end
3+
# To faclitate HTTP semantic convention stability migration, we are using
4+
# appraisal to test the different semantic convention modes along with different
5+
# gem versions. For more information on the semantic convention modes, see:
6+
# https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/
7+
8+
semconv_stability = %w[dup stable old]
9+
10+
semconv_stability.each do |mode|
11+
appraise "ethon-0.16.0-#{mode}" do
12+
gem 'ethon', '~> 0.16.0'
13+
end
614

7-
appraise 'ethon-latest' do
8-
gem 'ethon'
15+
appraise "ethon-#{mode}" do
16+
gem 'ethon'
17+
end
918
end

instrumentation/ethon/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,19 @@ The `opentelemetry-instrumentation-all` gem is distributed under the Apache 2.0
5252
[community-meetings]: https://github.com/open-telemetry/community#community-meetings
5353
[slack-channel]: https://cloud-native.slack.com/archives/C01NWKKMKMY
5454
[discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions
55+
56+
## HTTP semantic convention stability
57+
58+
In the OpenTelemetry ecosystem, HTTP semantic conventions have now reached a stable state. However, the initial Ethon instrumentation was introduced before this stability was achieved, which resulted in HTTP attributes being based on an older version of the semantic conventions.
59+
60+
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.
61+
62+
When setting the value for `OTEL_SEMCONV_STABILITY_OPT_IN`, you can specify which conventions you wish to adopt:
63+
64+
- `http` - Emits the stable HTTP and networking conventions and ceases emitting the old conventions previously emitted by the instrumentation.
65+
- `http/dup` - Emits both the old and stable HTTP and networking conventions, enabling a phased rollout of the stable semantic conventions.
66+
- Default behavior (in the absence of either value) is to continue emitting the old HTTP and networking conventions the instrumentation previously emitted.
67+
68+
During the transition from old to stable conventions, Ethon instrumentation code comes in three patch versions: `dup`, `old`, and `stable`. These versions are identical except for the attributes they send. Any changes to Ethon instrumentation should consider all three patches.
69+
70+
For additional information on migration, please refer to our [documentation](https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/).

instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/instrumentation.rb

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ module Ethon
1111
# instrumentation
1212
class Instrumentation < OpenTelemetry::Instrumentation::Base
1313
install do |_config|
14-
require_dependencies
15-
add_patches
14+
patch_type = determine_semconv
15+
send(:"require_dependencies_#{patch_type}")
16+
send(:"add_patches_#{patch_type}")
1617
end
1718

1819
present do
@@ -23,13 +24,46 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base
2324

2425
private
2526

26-
def require_dependencies
27-
require_relative 'patches/easy'
27+
def determine_semconv
28+
stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '')
29+
values = stability_opt_in.split(',').map(&:strip)
30+
31+
if values.include?('http/dup')
32+
'dup'
33+
elsif values.include?('http')
34+
'stable'
35+
else
36+
'old'
37+
end
38+
end
39+
40+
def require_dependencies_dup
41+
require_relative 'patches/dup/easy'
42+
require_relative 'patches/multi'
43+
end
44+
45+
def require_dependencies_stable
46+
require_relative 'patches/stable/easy'
2847
require_relative 'patches/multi'
2948
end
3049

31-
def add_patches
32-
::Ethon::Easy.prepend(Patches::Easy)
50+
def require_dependencies_old
51+
require_relative 'patches/old/easy'
52+
require_relative 'patches/multi'
53+
end
54+
55+
def add_patches_dup
56+
::Ethon::Easy.prepend(Patches::Dup::Easy)
57+
::Ethon::Multi.prepend(Patches::Multi)
58+
end
59+
60+
def add_patches_stable
61+
::Ethon::Easy.prepend(Patches::Stable::Easy)
62+
::Ethon::Multi.prepend(Patches::Multi)
63+
end
64+
65+
def add_patches_old
66+
::Ethon::Easy.prepend(Patches::Old::Easy)
3367
::Ethon::Multi.prepend(Patches::Multi)
3468
end
3569
end
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 Ethon
10+
module Patches
11+
# Module using old and stable HTTP semantic conventions
12+
module Dup
13+
# Ethon::Easy patch for instrumentation
14+
module Easy
15+
ACTION_NAMES_TO_HTTP_METHODS = Hash.new do |h, k|
16+
# #to_s is required because user input could be symbol or string
17+
h[k] = k.to_s.upcase
18+
end
19+
HTTP_METHODS_TO_SPAN_NAMES = Hash.new do |h, k|
20+
h[k] = k.to_s
21+
h[k] = 'HTTP' if k == '_OTHER'
22+
end
23+
24+
# Constant for the HTTP status range
25+
HTTP_STATUS_SUCCESS_RANGE = (100..399)
26+
27+
def http_request(url, action_name, options = {})
28+
@otel_method = ACTION_NAMES_TO_HTTP_METHODS[action_name]
29+
super
30+
end
31+
32+
def headers=(headers)
33+
# Store headers to call this method again when span is ready
34+
@otel_original_headers = headers
35+
super
36+
end
37+
38+
def perform
39+
otel_before_request
40+
super
41+
end
42+
43+
def complete
44+
begin
45+
response_options = mirror.options
46+
response_code = (response_options[:response_code] || response_options[:code]).to_i
47+
if response_code.zero?
48+
return_code = response_options[:return_code]
49+
message = return_code ? ::Ethon::Curl.easy_strerror(return_code) : 'unknown reason'
50+
@otel_span.status = OpenTelemetry::Trace::Status.error("Request has failed: #{message}")
51+
else
52+
@otel_span.set_attribute('http.status_code', response_code)
53+
@otel_span.set_attribute('http.response.status_code', response_code)
54+
@otel_span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response_code.to_i)
55+
end
56+
ensure
57+
@otel_span&.finish
58+
@otel_span = nil
59+
end
60+
super
61+
end
62+
63+
def reset
64+
super
65+
ensure
66+
@otel_span = nil
67+
@otel_method = nil
68+
@otel_original_headers = nil
69+
end
70+
71+
def otel_before_request
72+
method = '_OTHER' # Could be GET or not HTTP at all
73+
method = @otel_method if instance_variable_defined?(:@otel_method) && !@otel_method.nil?
74+
75+
@otel_span = tracer.start_span(
76+
HTTP_METHODS_TO_SPAN_NAMES[method],
77+
attributes: span_creation_attributes(method),
78+
kind: :client
79+
)
80+
81+
@otel_original_headers ||= {}
82+
OpenTelemetry::Trace.with_span(@otel_span) do
83+
OpenTelemetry.propagation.inject(@otel_original_headers)
84+
end
85+
self.headers = @otel_original_headers
86+
end
87+
88+
def otel_span_started?
89+
instance_variable_defined?(:@otel_span) && !@otel_span.nil?
90+
end
91+
92+
private
93+
94+
def span_creation_attributes(method)
95+
http_method = (method == '_OTHER' ? 'N/A' : method)
96+
instrumentation_attrs = {
97+
'http.method' => http_method,
98+
'http.request.method' => method
99+
}
100+
101+
uri = _otel_cleanse_uri(url)
102+
if uri
103+
instrumentation_attrs['http.url'] = uri.to_s
104+
instrumentation_attrs['url.full'] = uri.to_s
105+
instrumentation_attrs['net.peer.name'] = uri.host if uri.host
106+
instrumentation_attrs['server.address'] = uri.host if uri.host
107+
end
108+
109+
config = Ethon::Instrumentation.instance.config
110+
instrumentation_attrs['peer.service'] = config[:peer_service] if config[:peer_service]
111+
instrumentation_attrs.merge!(
112+
OpenTelemetry::Common::HTTP::ClientContext.attributes
113+
)
114+
end
115+
116+
# Returns a URL string with userinfo removed.
117+
#
118+
# @param [String] url The URL string to cleanse.
119+
#
120+
# @return [String] the cleansed URL.
121+
def _otel_cleanse_uri(url)
122+
cleansed_url = URI.parse(url)
123+
cleansed_url.password = nil
124+
cleansed_url.user = nil
125+
cleansed_url
126+
rescue URI::Error
127+
nil
128+
end
129+
130+
def tracer
131+
Ethon::Instrumentation.instance.tracer
132+
end
133+
end
134+
end
135+
end
136+
end
137+
end
138+
end

instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb

Lines changed: 0 additions & 127 deletions
This file was deleted.

0 commit comments

Comments
 (0)