Skip to content

Commit e4f9fb5

Browse files
feat: NET::HTTP semantic convention stability migration (#1572)
* feat: NET::HTTP semantic convention stability migration * fix: rubocop * fix: refactor attributes merge * Fix CI * Fix: CI on old rubies
1 parent 5d5ce06 commit e4f9fb5

File tree

11 files changed

+1156
-119
lines changed

11 files changed

+1156
-119
lines changed

instrumentation/net_http/Appraisals

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
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 "net-http-#{mode}" do
16+
# NOOP - net-http is part of the Ruby standard library.
17+
# We are only using Appraisals to allow testing of the
18+
# different stability modes. The file and the Appraisal
19+
# gem can be removed once semconv migration is complete.
20+
end
21+
end

instrumentation/net_http/Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ source 'https://rubygems.org'
99
gemspec
1010

1111
group :test do
12+
gem 'appraisal', '~> 2.5'
1213
gem 'bundler', '~> 2.4'
1314
gem 'minitest', '~> 5.0'
1415
gem 'opentelemetry-sdk', '~> 1.1'

instrumentation/net_http/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,19 @@ Apache 2.0 license. See [LICENSE][license-github] for more information.
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 Net::HTTP 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, Net::HTTP instrumentation code comes in three patch versions: `dup`, `old`, and `stable`. These versions are identical except for the attributes they send. Any changes to Net::HTTP 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/net_http/lib/opentelemetry/instrumentation/net/http/instrumentation.rb

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ module HTTP
1212
# 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
@@ -30,12 +31,41 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base
3031

3132
private
3233

33-
def require_dependencies
34-
require_relative 'patches/instrumentation'
34+
def determine_semconv
35+
stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '')
36+
values = stability_opt_in.split(',').map(&:strip)
37+
38+
if values.include?('http/dup')
39+
'dup'
40+
elsif values.include?('http')
41+
'stable'
42+
else
43+
'old'
44+
end
45+
end
46+
47+
def require_dependencies_dup
48+
require_relative 'patches/dup/instrumentation'
49+
end
50+
51+
def require_dependencies_old
52+
require_relative 'patches/old/instrumentation'
53+
end
54+
55+
def require_dependencies_stable
56+
require_relative 'patches/stable/instrumentation'
57+
end
58+
59+
def patch_dup
60+
::Net::HTTP.prepend(Patches::Dup::Instrumentation)
61+
end
62+
63+
def patch_old
64+
::Net::HTTP.prepend(Patches::Old::Instrumentation)
3565
end
3666

37-
def patch
38-
::Net::HTTP.prepend(Patches::Instrumentation)
67+
def patch_stable
68+
::Net::HTTP.prepend(Patches::Stable::Instrumentation)
3969
end
4070
end
4171
end
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 Net
10+
module HTTP
11+
module Patches
12+
module Dup
13+
# Module to prepend to Net::HTTP for instrumentation
14+
module Instrumentation
15+
USE_SSL_TO_SCHEME = { false => 'http', true => 'https' }.freeze
16+
17+
# Constant for the HTTP status range
18+
HTTP_STATUS_SUCCESS_RANGE = (100..399)
19+
20+
def request(req, body = nil, &)
21+
# Do not trace recursive call for starting the connection
22+
return super unless started?
23+
24+
return super if untraced?
25+
26+
attributes = {
27+
OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => req.method,
28+
OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => USE_SSL_TO_SCHEME[use_ssl?],
29+
OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => req.path,
30+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => @address,
31+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => @port,
32+
'http.request.method' => req.method,
33+
'url.scheme' => USE_SSL_TO_SCHEME[use_ssl?],
34+
'server.address' => @address,
35+
'server.port' => @port
36+
}
37+
path, query = split_path_and_query(req.path)
38+
attributes['url.path'] = path
39+
attributes['url.query'] = query if query
40+
41+
attributes.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
42+
43+
tracer.in_span(
44+
req.method,
45+
attributes: attributes,
46+
kind: :client
47+
) do |span|
48+
OpenTelemetry.propagation.inject(req)
49+
50+
super.tap do |response|
51+
annotate_span_with_response!(span, response)
52+
end
53+
end
54+
end
55+
56+
private
57+
58+
def connect
59+
return super if untraced?
60+
61+
if proxy?
62+
conn_address = proxy_address
63+
conn_port = proxy_port
64+
else
65+
conn_address = address
66+
conn_port = port
67+
end
68+
69+
attributes = {
70+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => conn_address,
71+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => conn_port,
72+
'server.address' => conn_address,
73+
'server.port' => conn_port
74+
}.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
75+
76+
if use_ssl? && proxy?
77+
span_name = 'CONNECT'
78+
span_kind = :client
79+
else
80+
span_name = 'connect'
81+
span_kind = :internal
82+
end
83+
84+
tracer.in_span(span_name, attributes: attributes, kind: span_kind) do
85+
super
86+
end
87+
end
88+
89+
def annotate_span_with_response!(span, response)
90+
return unless response&.code
91+
92+
status_code = response.code.to_i
93+
94+
span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, status_code)
95+
span.set_attribute('http.response.status_code', status_code)
96+
span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(status_code)
97+
end
98+
99+
def tracer
100+
Net::HTTP::Instrumentation.instance.tracer
101+
end
102+
103+
def untraced?
104+
untraced_context? || untraced_host?
105+
end
106+
107+
def untraced_host?
108+
return true if Net::HTTP::Instrumentation.instance.config[:untraced_hosts]&.any? do |host|
109+
host.is_a?(Regexp) ? host.match?(@address) : host == @address
110+
end
111+
112+
false
113+
end
114+
115+
def untraced_context?
116+
OpenTelemetry::Common::Utilities.untraced?
117+
end
118+
119+
def split_path_and_query(path)
120+
path_and_query = path.split('?')
121+
122+
[path_and_query[0], path_and_query[1]]
123+
end
124+
end
125+
end
126+
end
127+
end
128+
end
129+
end
130+
end

instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/patches/instrumentation.rb

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

0 commit comments

Comments
 (0)