Skip to content

Commit e940131

Browse files
samuel-williams-shopifyioquatix
authored andcommitted
Add support for new interface.
1 parent 2bca4c9 commit e940131

File tree

3 files changed

+241
-27
lines changed

3 files changed

+241
-27
lines changed

gems.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77

88
gemspec
99

10-
group :test do
11-
gem "console"
12-
gem "opentelemetry-sdk"
13-
end
14-
1510
group :maintenance, optional: true do
1611
gem "bake-modernize"
1712
gem "bake-gem"
@@ -27,4 +22,7 @@
2722

2823
gem "bake-test"
2924
gem "bake-test-external"
25+
26+
gem "opentelemetry-sdk"
27+
gem "opentelemetry-exporter-otlp"
3028
end

lib/traces/backend/open_telemetry/interface.rb

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@ def trace(name, attributes: nil, &block)
2121
end
2222

2323
def trace_context=(context)
24-
span_context = ::OpenTelemetry::Trace::SpanContext.new(
25-
trace_id: context.trace_id,
26-
span_id: context.parent_id,
27-
trace_flags: ::OpenTelemetry::Trace::TraceFlags.from_byte(context.flags),
28-
tracestate: context.state,
29-
remote: context.remote?
30-
)
31-
32-
span = ::OpenTelemetry::Trace.non_recording_span(span_context)
33-
context = ::OpenTelemetry::Trace.context_with_span(span)
34-
::OpenTelemetry::Context.attach(context)
24+
if context
25+
span_context = ::OpenTelemetry::Trace::SpanContext.new(
26+
trace_id: context.trace_id,
27+
span_id: context.parent_id,
28+
trace_flags: ::OpenTelemetry::Trace::TraceFlags.from_byte(context.flags),
29+
tracestate: context.state,
30+
remote: context.remote?
31+
)
32+
33+
span = ::OpenTelemetry::Trace.non_recording_span(span_context)
34+
context = ::OpenTelemetry::Trace.context_with_span(span)
35+
::OpenTelemetry::Context.attach(context)
36+
end
3537
end
3638

3739
def trace_context(span = ::OpenTelemetry::Trace.current_span)
@@ -51,6 +53,40 @@ def trace_context(span = ::OpenTelemetry::Trace.current_span)
5153
)
5254
end
5355
end
56+
57+
def current_context
58+
::OpenTelemetry::Context.current
59+
end
60+
61+
def with_context(context)
62+
if block_given?
63+
::OpenTelemetry::Context.with_current(context) do
64+
yield
65+
end
66+
else
67+
::OpenTelemetry::Context.attach(context)
68+
end
69+
end
70+
71+
def inject(headers = nil, context = nil)
72+
context ||= ::OpenTelemetry::Context.current
73+
headers ||= Hash.new
74+
75+
count = headers.count
76+
77+
::OpenTelemetry.propagation.inject(headers, context: context)
78+
79+
if count == headers.count
80+
# No injection was performed, so return nil:
81+
headers = nil
82+
end
83+
84+
return headers
85+
end
86+
87+
def extract(headers)
88+
::OpenTelemetry.propagation.extract(headers)
89+
end
5490
end
5591
end
5692

test/traces/backend/open_telemetry.rb

Lines changed: 191 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ def my_method_with_exception
2424
end
2525

2626
def my_span
27-
Traces.trace('my_span') {|span| return span}
27+
Traces.trace("my_span") {|span| return span}
2828
end
2929

3030
def my_context
31-
Traces.trace('my_context') {|span| return Traces.trace_context}
31+
Traces.trace("my_context") {|span| return Traces.trace_context}
3232
end
3333

3434
def my_span_and_context
35-
Traces.trace('my_span_and_context') {|span| return span, Traces.trace_context}
35+
Traces.trace("my_span_and_context") {|span| return span, Traces.trace_context}
3636
end
3737
end
3838

@@ -66,14 +66,26 @@ def my_span_and_context
6666
end
6767

6868
describe OpenTelemetry::Trace::Span do
69-
let(:span) {instance.my_span}
70-
71-
it "can assign name" do
72-
span.name = "new_name"
69+
it "can assign name while span is active" do
70+
span_name = nil
71+
72+
Traces.trace("test") do |span|
73+
span.name = "new_name"
74+
span_name = span.name
75+
end
76+
77+
expect(span_name).to be == "new_name"
7378
end
7479

75-
it "can assign attributes" do
76-
span["my_key"] = "tag_value"
80+
it "can assign attributes while span is active" do
81+
Traces.trace("test") do |span|
82+
# Both syntaxes should work without error:
83+
span["my_key"] = "tag_value"
84+
span.set_attribute("another_key", "another_value")
85+
86+
# Test passes if no exception is raised:
87+
expect(span).to be_a(::OpenTelemetry::Trace::Span)
88+
end
7789
end
7890
end
7991

@@ -82,15 +94,15 @@ def my_span_and_context
8294
let(:span) {span_and_context.first}
8395
let(:context) {span_and_context.last}
8496

85-
with '#trace_context' do
97+
with "#trace_context" do
8698
it "has a valid trace id" do
8799
expect(context).to have_attributes(
88100
trace_id: be != nil
89101
)
90102
end
91103
end
92104

93-
with '#trace_context=' do
105+
with "#trace_context=" do
94106
it "can update trace context" do
95107
Traces.trace_context = context
96108

@@ -106,4 +118,172 @@ def my_span_and_context
106118
end
107119
end
108120
end
121+
122+
describe "Context Propagation Methods" do
123+
with "#current_context" do
124+
it "returns current OpenTelemetry context" do
125+
current = Traces.current_context
126+
expect(current).to be_a(::OpenTelemetry::Context)
127+
end
128+
129+
it "returns different contexts in different spans" do
130+
context1 = nil
131+
context2 = nil
132+
133+
Traces.trace("span1") do
134+
context1 = Traces.current_context
135+
end
136+
137+
Traces.trace("span2") do
138+
context2 = Traces.current_context
139+
end
140+
141+
# Contexts should be different objects (different spans):
142+
expect(context1).not.to be_equal(context2)
143+
end
144+
end
145+
146+
with "#with_context" do
147+
it "executes block within specified context" do
148+
original_context = Traces.current_context
149+
test_context = ::OpenTelemetry::Context.empty
150+
executed = false
151+
152+
Traces.with_context(test_context) do
153+
executed = true
154+
expect(::OpenTelemetry::Context.current).to be_equal(test_context)
155+
end
156+
157+
expect(executed).to be == true
158+
# Context should be restored after block:
159+
expect(::OpenTelemetry::Context.current).to be_equal(original_context)
160+
end
161+
162+
it "permanently sets context when called without block" do
163+
original_context = Traces.current_context
164+
test_context = ::OpenTelemetry::Context.empty
165+
166+
token = Traces.with_context(test_context)
167+
expect(::OpenTelemetry::Context.current).to be_equal(test_context)
168+
169+
# Clean up by detaching:
170+
::OpenTelemetry::Context.detach(token)
171+
end
172+
end
173+
174+
with "#inject" do
175+
it "injects trace context into headers" do
176+
headers = {}
177+
178+
Traces.trace("test") do
179+
Traces.inject(headers)
180+
end
181+
182+
expect(headers).to have_keys(
183+
"traceparent" => be_a(String)
184+
)
185+
186+
traceparent = headers["traceparent"]
187+
expect(traceparent).to be =~ /^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/
188+
end
189+
190+
it "creates new headers hash when none provided" do
191+
Traces.trace("test") do
192+
headers = Traces.inject()
193+
expect(headers).to be_a(Hash)
194+
expect(headers["traceparent"]).to be =~ /^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/
195+
end
196+
end
197+
198+
it "uses specific context when provided" do
199+
headers = {}
200+
201+
Traces.trace("test") do
202+
specific_context = Traces.current_context
203+
Traces.inject(headers, specific_context)
204+
expect(headers["traceparent"]).to be =~ /^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/
205+
end
206+
end
207+
208+
it "returns nil when no headers provided and no active trace" do
209+
# Clear any active trace:
210+
::OpenTelemetry::Context.clear
211+
212+
result = Traces.inject()
213+
expect(result).to be == nil
214+
end
215+
216+
it "returns nil when headers provided but no active trace" do
217+
# Clear any active trace:
218+
::OpenTelemetry::Context.clear
219+
headers = {"existing" => "value"}
220+
221+
result = Traces.inject(headers)
222+
expect(result).to be == nil
223+
# Original headers should remain unchanged:
224+
expect(headers["existing"]).to be == "value"
225+
expect(headers.key?("traceparent")).to be == false
226+
end
227+
end
228+
229+
with "#extract" do
230+
it "extracts context from headers" do
231+
headers = {
232+
"traceparent" => "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
233+
}
234+
235+
context = Traces.extract(headers)
236+
expect(context).to be_a(::OpenTelemetry::Context)
237+
238+
# Verify we can use the extracted context:
239+
Traces.with_context(context) do
240+
span = ::OpenTelemetry::Trace.current_span
241+
# Convert binary trace_id to hex for comparison:
242+
trace_id_hex = span.context.trace_id.unpack1("H*")
243+
expect(trace_id_hex).to be == "4bf92f3577b34da6a3ce929d0e0e4736"
244+
end
245+
end
246+
247+
it "returns original context for invalid headers" do
248+
headers = {"traceparent" => "invalid"}
249+
original_context = ::OpenTelemetry::Context.current
250+
251+
result = Traces.extract(headers)
252+
expect(result).to be_equal(original_context)
253+
end
254+
255+
it "handles missing headers gracefully" do
256+
headers = {}
257+
original_context = ::OpenTelemetry::Context.current
258+
259+
result = Traces.extract(headers)
260+
expect(result).to be_equal(original_context)
261+
end
262+
end
263+
264+
with "round-trip inject/extract" do
265+
it "preserves context through inject and extract cycle" do
266+
original_headers = {}
267+
268+
# Create a trace and inject it:
269+
Traces.trace("test") do
270+
Traces.inject(original_headers)
271+
end
272+
273+
expect(original_headers).to have_keys(
274+
"traceparent" => be_a(String)
275+
)
276+
277+
# Extract the context:
278+
extracted_context = Traces.extract(original_headers)
279+
expect(extracted_context).to be_a(::OpenTelemetry::Context)
280+
281+
# Use extracted context to create another trace:
282+
Traces.with_context(extracted_context) do
283+
span = ::OpenTelemetry::Trace.current_span
284+
expect(span.context.remote?).to be == true
285+
end
286+
end
287+
end
288+
end
109289
end

0 commit comments

Comments
 (0)