Skip to content

Commit c3a7804

Browse files
committed
feat: NET::HTTP semantic convention stability migration
1 parent 0d74059 commit c3a7804

File tree

11 files changed

+1126
-119
lines changed

11 files changed

+1126
-119
lines changed
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
3545
end
3646

37-
def patch
38-
::Net::HTTP.prepend(Patches::Instrumentation)
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)
65+
end
66+
67+
def patch_stable
68+
::Net::HTTP.prepend(Patches::Stable::Instrumentation)
3969
end
4070
end
4171
end
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
}.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
37+
38+
path, query = split_path_and_query(req.path)
39+
attributes['url.path'] = path unless attributes.key?('url.path')
40+
attributes['url.query'] = query if query
41+
42+
tracer.in_span(
43+
req.method,
44+
attributes: attributes,
45+
kind: :client
46+
) do |span|
47+
OpenTelemetry.propagation.inject(req)
48+
49+
super.tap do |response|
50+
annotate_span_with_response!(span, response)
51+
end
52+
end
53+
end
54+
55+
private
56+
57+
def connect
58+
return super if untraced?
59+
60+
if proxy?
61+
conn_address = proxy_address
62+
conn_port = proxy_port
63+
else
64+
conn_address = address
65+
conn_port = port
66+
end
67+
68+
attributes = {
69+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => conn_address,
70+
OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => conn_port,
71+
'server.address' => conn_address,
72+
'server.port' => conn_port
73+
}.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
74+
75+
if use_ssl? && proxy?
76+
span_name = 'CONNECT'
77+
span_kind = :client
78+
else
79+
span_name = 'connect'
80+
span_kind = :internal
81+
end
82+
83+
tracer.in_span(span_name, attributes: attributes, kind: span_kind) do
84+
super
85+
end
86+
end
87+
88+
def annotate_span_with_response!(span, response)
89+
return unless response&.code
90+
91+
status_code = response.code.to_i
92+
93+
span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, status_code)
94+
span.set_attribute('http.response.status_code', status_code)
95+
span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(status_code)
96+
end
97+
98+
def tracer
99+
Net::HTTP::Instrumentation.instance.tracer
100+
end
101+
102+
def untraced?
103+
untraced_context? || untraced_host?
104+
end
105+
106+
def untraced_host?
107+
return true if Net::HTTP::Instrumentation.instance.config[:untraced_hosts]&.any? do |host|
108+
host.is_a?(Regexp) ? host.match?(@address) : host == @address
109+
end
110+
111+
false
112+
end
113+
114+
def untraced_context?
115+
OpenTelemetry::Common::Utilities.untraced?
116+
end
117+
118+
def split_path_and_query(path)
119+
path_and_query = path.split('?')
120+
121+
[path_and_query[0], path_and_query[1]]
122+
end
123+
end
124+
end
125+
end
126+
end
127+
end
128+
end
129+
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)