Skip to content

Commit c3edc8d

Browse files
feat: HTTPClient semantic convention stability migration (#1588)
* feat: HTTPClient semantic convention stability migration * fix: test update --------- Co-authored-by: Kayla Reopelle <[email protected]>
1 parent 774496f commit c3edc8d

File tree

18 files changed

+797
-106
lines changed

18 files changed

+797
-106
lines changed

instrumentation/http_client/Appraisals

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
#
55
# SPDX-License-Identifier: Apache-2.0
66

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

instrumentation/http_client/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,19 @@ The `opentelemetry-instrumentation-http_client` gem is distributed under the Apa
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 HttpClient 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, HttpClient instrumentation code comes in three patch versions: `dup`, `old`, and `stable`. These versions are identical except for the attributes they send. Any changes to HttpClient 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/http_client/lib/opentelemetry/instrumentation/http_client/instrumentation.rb

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ module HttpClient
1212
# The Instrumentation class contains logic to detect and install the HttpClient instrumentation
1313
class Instrumentation < OpenTelemetry::Instrumentation::Base
1414
install do |_config|
15-
require_dependencies
16-
patch
15+
patch_type = determine_semconv
16+
send(:"require_dependencies_#{patch_type}")
17+
send(:"patch_#{patch_type}")
1718
end
1819

1920
present do
@@ -22,14 +23,47 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base
2223

2324
private
2425

25-
def patch
26-
::HTTPClient.prepend(Patches::Client)
27-
::HTTPClient::Session.prepend(Patches::Session)
26+
def determine_semconv
27+
stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '')
28+
values = stability_opt_in.split(',').map(&:strip)
29+
30+
if values.include?('http/dup')
31+
'dup'
32+
elsif values.include?('http')
33+
'stable'
34+
else
35+
'old'
36+
end
37+
end
38+
39+
def patch_dup
40+
::HTTPClient.prepend(Patches::Dup::Client)
41+
::HTTPClient::Session.prepend(Patches::Dup::Session)
42+
end
43+
44+
def patch_old
45+
::HTTPClient.prepend(Patches::Old::Client)
46+
::HTTPClient::Session.prepend(Patches::Old::Session)
47+
end
48+
49+
def patch_stable
50+
::HTTPClient.prepend(Patches::Stable::Client)
51+
::HTTPClient::Session.prepend(Patches::Stable::Session)
52+
end
53+
54+
def require_dependencies_dup
55+
require_relative 'patches/dup/client'
56+
require_relative 'patches/dup/session'
57+
end
58+
59+
def require_dependencies_old
60+
require_relative 'patches/old/client'
61+
require_relative 'patches/old/session'
2862
end
2963

30-
def require_dependencies
31-
require_relative 'patches/client'
32-
require_relative 'patches/session'
64+
def require_dependencies_stable
65+
require_relative 'patches/stable/client'
66+
require_relative 'patches/stable/session'
3367
end
3468
end
3569
end

instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/patches/client.rb

Lines changed: 0 additions & 58 deletions
This file was deleted.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 HttpClient
10+
module Patches
11+
module Dup
12+
# Module to prepend to HTTPClient for instrumentation
13+
module Client
14+
# Constant for the HTTP status range
15+
HTTP_STATUS_SUCCESS_RANGE = (100..399)
16+
17+
private
18+
19+
def do_get_block(req, proxy, conn, &)
20+
uri = req.header.request_uri
21+
url = "#{uri.scheme}://#{uri.host}"
22+
request_method = req.header.request_method
23+
24+
attributes = {
25+
'http.method' => request_method,
26+
'http.scheme' => uri.scheme,
27+
'http.target' => uri.path,
28+
'http.url' => url,
29+
'net.peer.name' => uri.host,
30+
'net.peer.port' => uri.port,
31+
# stable semantic conventions
32+
'http.request.method' => request_method,
33+
'url.scheme' => uri.scheme,
34+
'url.path' => uri.path,
35+
'url.full' => url,
36+
'server.address' => uri.host,
37+
'server.port' => uri.port
38+
}.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
39+
40+
attributes['url.query'] = uri.query unless uri.query.nil?
41+
42+
tracer.in_span(request_method, attributes: attributes, kind: :client) do |span|
43+
OpenTelemetry.propagation.inject(req.header)
44+
super.tap do
45+
response = conn.pop
46+
annotate_span_with_response!(span, response)
47+
conn.push response
48+
end
49+
end
50+
end
51+
52+
def annotate_span_with_response!(span, response)
53+
return unless response&.status_code
54+
55+
status_code = response.status_code.to_i
56+
57+
span.set_attribute('http.status_code', status_code)
58+
span.set_attribute('http.response.status_code', status_code)
59+
span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(status_code)
60+
end
61+
62+
def tracer
63+
HttpClient::Instrumentation.instance.tracer
64+
end
65+
end
66+
end
67+
end
68+
end
69+
end
70+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 HttpClient
10+
module Patches
11+
module Dup
12+
# Module to prepend to HTTPClient::Session for instrumentation
13+
module Session
14+
def connect
15+
site = @proxy || @dest
16+
url = site.addr
17+
18+
attributes = { 'http.url' => url, 'url.full' => url }.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
19+
tracer.in_span('CONNECT', attributes: attributes) do
20+
super
21+
end
22+
end
23+
24+
private
25+
26+
def tracer
27+
HttpClient::Instrumentation.instance.tracer
28+
end
29+
end
30+
end
31+
end
32+
end
33+
end
34+
end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 HttpClient
10+
module Patches
11+
module Old
12+
# Module to prepend to HTTPClient for instrumentation
13+
module Client
14+
# Constant for the HTTP status range
15+
HTTP_STATUS_SUCCESS_RANGE = (100..399)
16+
17+
private
18+
19+
def do_get_block(req, proxy, conn, &)
20+
uri = req.header.request_uri
21+
url = "#{uri.scheme}://#{uri.host}"
22+
request_method = req.header.request_method
23+
24+
attributes = {
25+
'http.method' => request_method,
26+
'http.scheme' => uri.scheme,
27+
'http.target' => uri.path,
28+
'http.url' => url,
29+
'net.peer.name' => uri.host,
30+
'net.peer.port' => uri.port
31+
}.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
32+
33+
tracer.in_span("HTTP #{request_method}", attributes: attributes, kind: :client) do |span|
34+
OpenTelemetry.propagation.inject(req.header)
35+
super.tap do
36+
response = conn.pop
37+
annotate_span_with_response!(span, response)
38+
conn.push response
39+
end
40+
end
41+
end
42+
43+
def annotate_span_with_response!(span, response)
44+
return unless response&.status_code
45+
46+
status_code = response.status_code.to_i
47+
48+
span.set_attribute('http.status_code', status_code)
49+
span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(status_code)
50+
end
51+
52+
def tracer
53+
HttpClient::Instrumentation.instance.tracer
54+
end
55+
end
56+
end
57+
end
58+
end
59+
end
60+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 HttpClient
10+
module Patches
11+
module Old
12+
# Module to prepend to HTTPClient::Session for instrumentation
13+
module Session
14+
def connect
15+
site = @proxy || @dest
16+
url = site.addr
17+
18+
attributes = { 'http.url' => url }.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
19+
tracer.in_span('HTTP CONNECT', attributes: attributes) do
20+
super
21+
end
22+
end
23+
24+
private
25+
26+
def tracer
27+
HttpClient::Instrumentation.instance.tracer
28+
end
29+
end
30+
end
31+
end
32+
end
33+
end
34+
end

instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/patches/session.rb

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

0 commit comments

Comments
 (0)