Skip to content

Commit 498e143

Browse files
committed
feat: Faraday semconv stability opt in
1 parent 9e8fe43 commit 498e143

File tree

15 files changed

+936
-139
lines changed

15 files changed

+936
-139
lines changed

instrumentation/faraday/Appraisals

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
# frozen_string_literal: true
1+
# To faclitate HTTP semantic convention stability migration, we are using
2+
# appraisal to test the different semantic convention modes along with different
3+
# gem versions. For more information on the semantic convention modes, see:
4+
# https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/
25

3-
# Copyright The OpenTelemetry Authors
4-
#
5-
# SPDX-License-Identifier: Apache-2.0
6+
versions = %w[1.0 2.0]
7+
semconv_stability = %w[old stable dup]
68

7-
%w[1.0 2.0].each do |version|
8-
appraise "faraday-#{version}" do
9-
gem 'faraday', "~> #{version}"
9+
semconv_stability.each do |mode|
10+
versions.each do |version|
11+
appraise "faraday-#{version}-#{mode}" do
12+
gem 'faraday', "~> #{version}"
13+
end
1014
end
11-
end
1215

13-
appraise 'faraday-latest' do
14-
gem 'faraday'
16+
appraise "faraday-latest-#{mode}" do
17+
gem 'faraday'
18+
end
1519
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: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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.to_s, 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.request.method' => http_method,
65+
'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url.to_s),
66+
'url.full' => OpenTelemetry::Common::Utilities.cleanse_url(url.to_s),
67+
'faraday.adapter.name' => app.class.name
68+
}
69+
attrs['net.peer.name'] = url.host if url.host
70+
attrs['server.address'] = url.host if url.host
71+
attrs['peer.service'] = config[:peer_service] if config[:peer_service]
72+
73+
attrs.merge!(
74+
OpenTelemetry::Common::HTTP::ClientContext.attributes
75+
)
76+
end
77+
78+
# Versions prior to 1.0 do not define an accessor for app
79+
attr_reader :app if Gem::Version.new(Faraday::VERSION) < Gem::Version.new('1.0.0')
80+
81+
def tracer
82+
Faraday::Instrumentation.instance.tracer
83+
end
84+
85+
def trace_response(span, status)
86+
span.set_attribute('http.status_code', status)
87+
span.set_attribute('http.response.status_code', status)
88+
span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(status.to_i)
89+
end
90+
end
91+
end
92+
end
93+
end
94+
end
95+
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)