Skip to content

Commit 19bec84

Browse files
feat: Faraday semantic convention stability opt in (#1592)
* feat: Faraday semconv stability opt in * fix: rubocop * Fix: remove redundant .to_s and refactor * Update instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/middlewares/dup/tracer_middleware.rb Co-authored-by: Kayla Reopelle <[email protected]> --------- Co-authored-by: Kayla Reopelle <[email protected]>
1 parent facd665 commit 19bec84

File tree

15 files changed

+941
-135
lines changed

15 files changed

+941
-135
lines changed

instrumentation/faraday/Appraisals

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

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

13-
appraise 'faraday-latest' do
14-
gem 'faraday'
22+
appraise "faraday-latest-#{mode}" do
23+
gem 'faraday'
24+
end
1525
end

instrumentation/faraday/README.md

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

instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/instrumentation.rb

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base
1313
MINIMUM_VERSION = Gem::Version.new('1.0')
1414

1515
install do |_config|
16-
require_dependencies
17-
register_tracer_middleware
18-
use_middleware_by_default
16+
patch_type = determine_semconv
17+
send(:"require_dependencies_#{patch_type}")
18+
send(:"register_tracer_middleware_#{patch_type}")
19+
send(:"use_middleware_by_default_#{patch_type}")
1920
end
2021

2122
compatible do
@@ -36,19 +37,62 @@ def gem_version
3637
Gem::Version.new(::Faraday::VERSION)
3738
end
3839

39-
def require_dependencies
40-
require_relative 'middlewares/tracer_middleware'
41-
require_relative 'patches/connection'
40+
def determine_semconv
41+
stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '')
42+
values = stability_opt_in.split(',').map(&:strip)
43+
44+
if values.include?('http/dup')
45+
'dup'
46+
elsif values.include?('http')
47+
'stable'
48+
else
49+
'old'
50+
end
51+
end
52+
53+
def require_dependencies_dup
54+
require_relative 'middlewares/dup/tracer_middleware'
55+
require_relative 'patches/dup/connection'
56+
end
57+
58+
def require_dependencies_old
59+
require_relative 'middlewares/old/tracer_middleware'
60+
require_relative 'patches/old/connection'
4261
end
4362

44-
def register_tracer_middleware
63+
def require_dependencies_stable
64+
require_relative 'middlewares/stable/tracer_middleware'
65+
require_relative 'patches/stable/connection'
66+
end
67+
68+
def register_tracer_middleware_dup
4569
::Faraday::Middleware.register_middleware(
46-
open_telemetry: Middlewares::TracerMiddleware
70+
open_telemetry: Middlewares::Dup::TracerMiddleware
4771
)
4872
end
4973

50-
def use_middleware_by_default
51-
::Faraday::Connection.prepend(Patches::Connection)
74+
def register_tracer_middleware_old
75+
::Faraday::Middleware.register_middleware(
76+
open_telemetry: Middlewares::Old::TracerMiddleware
77+
)
78+
end
79+
80+
def register_tracer_middleware_stable
81+
::Faraday::Middleware.register_middleware(
82+
open_telemetry: Middlewares::Stable::TracerMiddleware
83+
)
84+
end
85+
86+
def use_middleware_by_default_dup
87+
::Faraday::Connection.prepend(Patches::Dup::Connection)
88+
end
89+
90+
def use_middleware_by_default_old
91+
::Faraday::Connection.prepend(Patches::Old::Connection)
92+
end
93+
94+
def use_middleware_by_default_stable
95+
::Faraday::Connection.prepend(Patches::Stable::Connection)
5296
end
5397
end
5498
end
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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 Faraday
10+
module Middlewares
11+
module Dup
12+
# TracerMiddleware propagates context and instruments Faraday requests
13+
# by way of its middleware system
14+
class TracerMiddleware < ::Faraday::Middleware
15+
HTTP_METHODS_SYMBOL_TO_STRING = {
16+
connect: 'CONNECT',
17+
delete: 'DELETE',
18+
get: 'GET',
19+
head: 'HEAD',
20+
options: 'OPTIONS',
21+
patch: 'PATCH',
22+
post: 'POST',
23+
put: 'PUT',
24+
trace: 'TRACE'
25+
}.freeze
26+
27+
# Constant for the HTTP status range
28+
HTTP_STATUS_SUCCESS_RANGE = (100..399)
29+
30+
def call(env)
31+
http_method = HTTP_METHODS_SYMBOL_TO_STRING[env.method]
32+
config = Faraday::Instrumentation.instance.config
33+
34+
attributes = span_creation_attributes(
35+
http_method: http_method, url: env.url, config: config
36+
)
37+
38+
OpenTelemetry::Common::HTTP::ClientContext.with_attributes(attributes) do |attrs, _|
39+
tracer.in_span(
40+
http_method, attributes: attrs, kind: config.fetch(:span_kind)
41+
) do |span|
42+
OpenTelemetry.propagation.inject(env.request_headers)
43+
44+
if config[:enable_internal_instrumentation] == false
45+
OpenTelemetry::Common::Utilities.untraced do
46+
app.call(env).on_complete { |resp| trace_response(span, resp.status) }
47+
end
48+
else
49+
app.call(env).on_complete { |resp| trace_response(span, resp.status) }
50+
end
51+
rescue ::Faraday::Error => e
52+
trace_response(span, e.response[:status]) if e.response
53+
54+
raise
55+
end
56+
end
57+
end
58+
59+
private
60+
61+
def span_creation_attributes(http_method:, url:, config:)
62+
cleansed_url = OpenTelemetry::Common::Utilities.cleanse_url(url.to_s)
63+
attrs = {
64+
'http.method' => http_method,
65+
'http.request.method' => http_method,
66+
'http.url' => cleansed_url,
67+
'url.full' => cleansed_url,
68+
'faraday.adapter.name' => app.class.name
69+
}
70+
if url.host
71+
attrs['net.peer.name'] = url.host
72+
attrs['server.address'] = url.host
73+
end
74+
attrs['peer.service'] = config[:peer_service] if config[:peer_service]
75+
76+
attrs.merge!(
77+
OpenTelemetry::Common::HTTP::ClientContext.attributes
78+
)
79+
end
80+
81+
# Versions prior to 1.0 do not define an accessor for app
82+
attr_reader :app if Gem::Version.new(Faraday::VERSION) < Gem::Version.new('1.0.0')
83+
84+
def tracer
85+
Faraday::Instrumentation.instance.tracer
86+
end
87+
88+
def trace_response(span, status)
89+
span.set_attribute('http.status_code', status)
90+
span.set_attribute('http.response.status_code', status)
91+
span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(status.to_i)
92+
end
93+
end
94+
end
95+
end
96+
end
97+
end
98+
end
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 Faraday
10+
module Middlewares
11+
module Old
12+
# TracerMiddleware propagates context and instruments Faraday requests
13+
# by way of its middleware system
14+
class TracerMiddleware < ::Faraday::Middleware
15+
HTTP_METHODS_SYMBOL_TO_STRING = {
16+
connect: 'CONNECT',
17+
delete: 'DELETE',
18+
get: 'GET',
19+
head: 'HEAD',
20+
options: 'OPTIONS',
21+
patch: 'PATCH',
22+
post: 'POST',
23+
put: 'PUT',
24+
trace: 'TRACE'
25+
}.freeze
26+
27+
# Constant for the HTTP status range
28+
HTTP_STATUS_SUCCESS_RANGE = (100..399)
29+
30+
def call(env)
31+
http_method = HTTP_METHODS_SYMBOL_TO_STRING[env.method]
32+
config = Faraday::Instrumentation.instance.config
33+
34+
attributes = span_creation_attributes(
35+
http_method: http_method, url: env.url, config: config
36+
)
37+
38+
OpenTelemetry::Common::HTTP::ClientContext.with_attributes(attributes) do |attrs, _|
39+
tracer.in_span(
40+
"HTTP #{http_method}", attributes: attrs, kind: config.fetch(:span_kind)
41+
) do |span|
42+
OpenTelemetry.propagation.inject(env.request_headers)
43+
44+
if config[:enable_internal_instrumentation] == false
45+
OpenTelemetry::Common::Utilities.untraced do
46+
app.call(env).on_complete { |resp| trace_response(span, resp.status) }
47+
end
48+
else
49+
app.call(env).on_complete { |resp| trace_response(span, resp.status) }
50+
end
51+
rescue ::Faraday::Error => e
52+
trace_response(span, e.response[:status]) if e.response
53+
54+
raise
55+
end
56+
end
57+
end
58+
59+
private
60+
61+
def span_creation_attributes(http_method:, url:, config:)
62+
attrs = {
63+
'http.method' => http_method,
64+
'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url.to_s),
65+
'faraday.adapter.name' => app.class.name
66+
}
67+
attrs['net.peer.name'] = url.host if url.host
68+
attrs['peer.service'] = config[:peer_service] if config[:peer_service]
69+
70+
attrs.merge!(
71+
OpenTelemetry::Common::HTTP::ClientContext.attributes
72+
)
73+
end
74+
75+
# Versions prior to 1.0 do not define an accessor for app
76+
attr_reader :app if Gem::Version.new(Faraday::VERSION) < Gem::Version.new('1.0.0')
77+
78+
def tracer
79+
Faraday::Instrumentation.instance.tracer
80+
end
81+
82+
def trace_response(span, status)
83+
span.set_attribute('http.status_code', status)
84+
span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(status.to_i)
85+
end
86+
end
87+
end
88+
end
89+
end
90+
end
91+
end

0 commit comments

Comments
 (0)