Skip to content

Commit aa0749e

Browse files
authored
Merge branch 'main' into gcp-lb-propagator
2 parents e972254 + f8d2551 commit aa0749e

File tree

13 files changed

+575
-65
lines changed

13 files changed

+575
-65
lines changed

.github/workflows/ossf-scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ jobs:
4343
# Upload the results to GitHub's code scanning dashboard (optional).
4444
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
4545
- name: "Upload to code-scanning"
46-
uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
46+
uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
4747
with:
4848
sarif_file: results.sarif

instrumentation/httpx/Appraisals

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,3 @@
77
appraise 'httpx-1' do
88
gem 'httpx', '~> 1.0'
99
end
10-
11-
appraise 'httpx-0' do
12-
gem 'httpx', '~> 0.24'
13-
end

instrumentation/httpx/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release History: opentelemetry-instrumentation-httpx
22

3+
### v0.2.1 / 2025-04-29
4+
5+
* FIXED: Httpx instrumentation trace context propagation
6+
37
### v0.2.0 / 2025-01-16
48

59
* BREAKING CHANGE: Set minimum supported version to Ruby 3.1

instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/plugin.rb

Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,83 +10,114 @@ module HTTPX
1010
module Plugin
1111
# Instruments around HTTPX's request/response lifecycle in order to generate
1212
# an OTEL trace.
13-
class RequestTracer
14-
# Constant for the HTTP status range
15-
HTTP_STATUS_SUCCESS_RANGE = (100..399)
13+
module RequestTracer
14+
module_function
15+
16+
# initializes tracing on the +request+.
17+
def call(request)
18+
span = nil
19+
20+
# request objects are reused, when already buffered requests get rerouted to a different
21+
# connection due to connection issues, or when they already got a response, but need to
22+
# be retried. In such situations, the original span needs to be extended for the former,
23+
# while a new is required for the latter.
24+
request.on(:idle) do
25+
span = nil
26+
end
27+
# the span is initialized when the request is buffered in the parser, which is the closest
28+
# one gets to actually sending the request.
29+
request.on(:headers) do
30+
next if span
31+
32+
span = initialize_span(request)
33+
end
34+
35+
request.on(:response) do |response|
36+
unless span
37+
next unless response.is_a?(::HTTPX::ErrorResponse) && response.error.respond_to?(:connection)
1638

17-
def initialize(request)
18-
@request = request
39+
# handles the case when the +error+ happened during name resolution, which means
40+
# that the tracing start point hasn't been triggered yet; in such cases, the approximate
41+
# initial resolving time is collected from the connection, and used as span start time,
42+
# and the tracing object in inserted before the on response callback is called.
43+
span = initialize_span(request, response.error.connection.init_time)
44+
45+
end
46+
47+
finish(response, span)
48+
end
1949
end
2050

21-
def call
22-
@request.on(:response, &method(:finish)) # rubocop:disable Performance/MethodObjectAsBlock
51+
def finish(response, span)
52+
if response.is_a?(::HTTPX::ErrorResponse)
53+
span.record_exception(response.error)
54+
span.status = Trace::Status.error(response.error.to_s)
55+
else
56+
span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, response.status)
57+
58+
if response.status.between?(400, 599)
59+
err = ::HTTPX::HTTPError.new(response)
60+
span.record_exception(err)
61+
span.status = Trace::Status.error(err.to_s)
62+
end
63+
end
2364

24-
uri = @request.uri
25-
request_method = @request.verb
26-
span_name = "HTTP #{request_method}"
65+
span.finish
66+
end
67+
68+
# return a span initialized with the +@request+ state.
69+
def initialize_span(request, start_time = ::Time.now)
70+
verb = request.verb
71+
uri = request.uri
72+
73+
config = HTTPX::Instrumentation.instance.config
2774

2875
attributes = {
2976
OpenTelemetry::SemanticConventions::Trace::HTTP_HOST => uri.host,
30-
OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => request_method,
77+
OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => verb,
3178
OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => uri.scheme,
3279
OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => uri.path,
3380
OpenTelemetry::SemanticConventions::Trace::HTTP_URL => "#{uri.scheme}://#{uri.host}",
3481
OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => uri.host,
3582
OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => uri.port
3683
}
37-
config = HTTPX::Instrumentation.instance.config
38-
attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service]
39-
attributes.merge!(
40-
OpenTelemetry::Common::HTTP::ClientContext.attributes
41-
)
4284

43-
@span = tracer.start_span(span_name, attributes: attributes, kind: :client)
44-
trace_ctx = OpenTelemetry::Trace.context_with_span(@span)
45-
@trace_token = OpenTelemetry::Context.attach(trace_ctx)
46-
47-
OpenTelemetry.propagation.inject(@request.headers)
48-
rescue StandardError => e
49-
OpenTelemetry.handle_error(exception: e)
50-
end
85+
attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service]
86+
attributes.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes)
5187

52-
def finish(response)
53-
return unless @span
88+
span = tracer.start_span("HTTP #{verb}", attributes: attributes, kind: :client, start_timestamp: start_time)
5489

55-
if response.is_a?(::HTTPX::ErrorResponse)
56-
@span.record_exception(response.error)
57-
@span.status = Trace::Status.error("Unhandled exception of type: #{response.error.class}")
58-
else
59-
@span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, response.status)
60-
@span.status = Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response.status)
90+
OpenTelemetry::Trace.with_span(span) do
91+
OpenTelemetry.propagation.inject(request.headers)
6192
end
6293

63-
OpenTelemetry::Context.detach(@trace_token) if @trace_token
64-
@span.finish
94+
span
95+
rescue StandardError => e
96+
OpenTelemetry.handle_error(exception: e)
6597
end
6698

67-
private
68-
6999
def tracer
70100
HTTPX::Instrumentation.instance.tracer
71101
end
72102
end
73103

74-
# HTTPX::Request overrides
104+
# Request patch to initiate the trace on initialization.
75105
module RequestMethods
76-
def __otel_enable_trace!
77-
return if @__otel_enable_trace
106+
def initialize(*)
107+
super
78108

79-
RequestTracer.new(self).call
80-
@__otel_enable_trace = true
109+
RequestTracer.call(self)
81110
end
82111
end
83112

84-
# HTTPX::Connection overrides
113+
# Connection patch to start monitoring on initialization.
85114
module ConnectionMethods
86-
def send(request)
87-
request.__otel_enable_trace!
115+
attr_reader :init_time
88116

117+
def initialize(*)
89118
super
119+
120+
@init_time = ::Time.now
90121
end
91122
end
92123
end

instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
module OpenTelemetry
88
module Instrumentation
99
module HTTPX
10-
VERSION = '0.2.0'
10+
VERSION = '0.2.1'
1111
end
1212
end
1313
end

instrumentation/httpx/test/instrumentation/plugin_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
OpenTelemetry::Trace::Status::ERROR
8383
)
8484
_(span.status.description).must_equal(
85-
'Unhandled exception of type: HTTPX::TimeoutError'
85+
'Timed out'
8686
)
8787
assert_requested(
8888
:get,

resources/aws/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release History: opentelemetry-resource-detector-aws
22

3+
### v0.2.0 / 2025-04-29
4+
5+
* ADDED: Contribute aws ecs resource detector
6+
37
### v0.1.0 / 2025-04-09
48

59
Initial release.

resources/aws/README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ require 'opentelemetry/sdk'
3030
require 'opentelemetry/resource/detector'
3131

3232
OpenTelemetry::SDK.configure do |c|
33-
c.resource = OpenTelemetry::Resource::Detector::AWS.detect
33+
# Specify which AWS resource detectors to use
34+
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ec2, :ecs])
35+
36+
# Or use just one detector
37+
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ec2])
38+
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ecs])
3439
end
3540
```
3641

@@ -52,7 +57,25 @@ Populates `cloud` and `host` for processes running on Amazon EC2, including abst
5257
| `host.name` | Value of hostname from `/latest/meta-data/hostname` request |
5358
| `host.type` | Value of `instanceType` from `/latest/dynamic/instance-identity/document` request |
5459

55-
Additional AWS platforms (ECS, EKS, Lambda) will be supported in future versions.
60+
### AWS ECS Detector
61+
62+
<!-- cspell:ignore launchtype awslogs -->
63+
Populates `cloud`, `container`, and AWS ECS-specific attributes for processes running on Amazon ECS.
64+
| Resource Attribute | Description |
65+
|--------------------|-------------|
66+
| `cloud.platform` | The cloud platform. In this context, it's always "aws_ecs" |
67+
| `cloud.provider` | The cloud provider. In this context, it's always "aws" |
68+
| `container.id` | The container ID from the `/proc/self/cgroup` file |
69+
| `container.name` | The hostname of the container |
70+
| `aws.ecs.container.arn` | The hostname of the container |
71+
| `aws.ecs.cluster.arn` | The ARN of the ECS cluster |
72+
| `aws.ecs.launchtype` | The launch type for the ECS task (e.g., "fargate" or "ec2") |
73+
| `aws.ecs.task.arn` | The ARN of the ECS task |
74+
| `aws.log.group.names` | The CloudWatch log group names (if awslogs driver is used) |
75+
| `aws.log.stream.names` | The CloudWatch log stream names (if awslogs driver is used) |
76+
| `aws.log.stream.arns` | The CloudWatch log stream ARNs (if awslogs driver is used) |
77+
78+
Additional AWS platforms (EKS, Lambda) will be supported in future versions.
5679

5780
## License
5881

resources/aws/lib/opentelemetry/resource/detector/aws.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# SPDX-License-Identifier: Apache-2.0
66

77
require 'opentelemetry/resource/detector/aws/ec2'
8+
require 'opentelemetry/resource/detector/aws/ecs'
89

910
module OpenTelemetry
1011
module Resource
@@ -13,12 +14,31 @@ module Detector
1314
module AWS
1415
extend self
1516

16-
def detect
17-
# This will be a composite of all the AWS platform detectors
18-
EC2.detect
17+
RESOURCE = OpenTelemetry::SDK::Resources::Resource
1918

20-
# For now, return the EC2 resource directly
21-
# In the future, we'll implement detection for EC2, ECS, EKS, etc.
19+
# Get resources from specified AWS resource detectors
20+
#
21+
# @param detectors [Array<Symbol>] List of detectors to use (e.g., :ec2)
22+
# @return [OpenTelemetry::SDK::Resources::Resource] The detected AWS resources
23+
def detect(detectors = [])
24+
return RESOURCE.create({}) if detectors.empty?
25+
26+
resources = detectors.map do |detector|
27+
case detector
28+
when :ec2
29+
EC2.detect
30+
when :ecs
31+
ECS.detect
32+
else
33+
OpenTelemetry.logger.warn("Unknown AWS resource detector: #{detector}")
34+
OpenTelemetry::SDK::Resources::Resource.create({})
35+
end
36+
end
37+
38+
# Merge all resources into a single resource
39+
resources.reduce(OpenTelemetry::SDK::Resources::Resource.create({})) do |merged, resource|
40+
merged.merge(resource)
41+
end
2242
end
2343
end
2444
end

0 commit comments

Comments
 (0)