Skip to content

Commit 6809aaa

Browse files
authored
Fix handling of spans at 2+ levels (#924)
* Fix handling of spans at 2+ levels * Update CHANGELOG Fixes #921
1 parent b7e1679 commit 6809aaa

File tree

7 files changed

+318
-9
lines changed

7 files changed

+318
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Unreleased
2+
3+
### Bug fixes
4+
5+
- Deeply nested spans are handled now when building up traces in `SpanProcessor` ([#924](https://github.com/getsentry/sentry-elixir/pull/924))
6+
17
## 11.0.1
28

39
#### Various improvements

lib/sentry/opentelemetry/span_storage.ex

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,20 @@ defmodule Sentry.OpenTelemetry.SpanStorage do
6464
def get_child_spans(parent_span_id, opts \\ []) do
6565
table_name = Keyword.get(opts, :table_name, default_table_name())
6666

67-
:ets.match_object(table_name, {{:child_span, parent_span_id, :_}, :_, :_})
68-
|> Enum.map(fn {_key, span_data, _stored_at} -> span_data end)
67+
get_all_descendants(parent_span_id, table_name)
68+
end
69+
70+
defp get_all_descendants(parent_span_id, table_name) do
71+
direct_children =
72+
:ets.match_object(table_name, {{:child_span, parent_span_id, :_}, :_, :_})
73+
|> Enum.map(fn {_key, span_data, _stored_at} -> span_data end)
74+
75+
nested_descendants =
76+
Enum.flat_map(direct_children, fn child ->
77+
get_all_descendants(child.span_id, table_name)
78+
end)
79+
80+
(direct_children ++ nested_descendants)
6981
|> Enum.sort_by(& &1.start_time)
7082
end
7183

test/sentry/opentelemetry/span_processor_test.exs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
183183
end
184184

185185
@tag span_storage: true
186-
test "nested child spans maintain sampling consistency" do
186+
test "nested child spans maintain hierarchy" do
187187
put_test_config(environment_name: "test", traces_sample_rate: 1.0)
188188

189189
Sentry.Test.start_collecting_sentry_reports()
@@ -193,22 +193,22 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
193193
Tracer.with_span "root_span" do
194194
Tracer.with_span "level_1_child" do
195195
Tracer.with_span "level_2_child" do
196-
Process.sleep(10)
196+
Process.sleep(1)
197197
end
198198

199199
Tracer.with_span "level_2_sibling" do
200-
Process.sleep(10)
200+
Process.sleep(1)
201201
end
202202
end
203203

204204
Tracer.with_span "level_1_sibling" do
205-
Process.sleep(10)
205+
Process.sleep(1)
206206
end
207207
end
208208

209209
assert [%Sentry.Transaction{} = transaction] = Sentry.Test.pop_sentry_transactions()
210210

211-
assert length(transaction.spans) == 2
211+
assert length(transaction.spans) == 4
212212

213213
trace_id = transaction.contexts.trace.trace_id
214214

@@ -217,7 +217,7 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
217217
end)
218218

219219
span_names = Enum.map(transaction.spans, & &1.op) |> Enum.sort()
220-
expected_names = ["level_1_child", "level_1_sibling"]
220+
expected_names = ["level_1_child", "level_1_sibling", "level_2_child", "level_2_sibling"]
221221
assert span_names == expected_names
222222
end
223223

test/sentry/opentelemetry/span_storage_test.exs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,206 @@ defmodule Sentry.OpenTelemetry.SpanStorageTest do
434434
end
435435
end
436436

437+
describe "nested span hierarchies" do
438+
@tag span_storage: true
439+
test "retrieves grand-children spans correctly", %{table_name: table_name} do
440+
root_span = %SpanRecord{
441+
span_id: "root123",
442+
parent_span_id: nil,
443+
trace_id: "trace123",
444+
name: "root_span",
445+
start_time: 1000,
446+
end_time: 2000
447+
}
448+
449+
child1_span = %SpanRecord{
450+
span_id: "child1",
451+
parent_span_id: "root123",
452+
trace_id: "trace123",
453+
name: "child_span_1",
454+
start_time: 1100,
455+
end_time: 1900
456+
}
457+
458+
child2_span = %SpanRecord{
459+
span_id: "child2",
460+
parent_span_id: "root123",
461+
trace_id: "trace123",
462+
name: "child_span_2",
463+
start_time: 1200,
464+
end_time: 1800
465+
}
466+
467+
grandchild1_span = %SpanRecord{
468+
span_id: "grandchild1",
469+
parent_span_id: "child1",
470+
trace_id: "trace123",
471+
name: "grandchild_span_1",
472+
start_time: 1150,
473+
end_time: 1250
474+
}
475+
476+
grandchild2_span = %SpanRecord{
477+
span_id: "grandchild2",
478+
parent_span_id: "child1",
479+
trace_id: "trace123",
480+
name: "grandchild_span_2",
481+
start_time: 1300,
482+
end_time: 1400
483+
}
484+
485+
grandchild3_span = %SpanRecord{
486+
span_id: "grandchild3",
487+
parent_span_id: "child2",
488+
trace_id: "trace123",
489+
name: "grandchild_span_3",
490+
start_time: 1250,
491+
end_time: 1350
492+
}
493+
494+
SpanStorage.store_span(root_span, table_name: table_name)
495+
SpanStorage.store_span(child1_span, table_name: table_name)
496+
SpanStorage.store_span(child2_span, table_name: table_name)
497+
SpanStorage.store_span(grandchild1_span, table_name: table_name)
498+
SpanStorage.store_span(grandchild2_span, table_name: table_name)
499+
SpanStorage.store_span(grandchild3_span, table_name: table_name)
500+
501+
all_descendants = SpanStorage.get_child_spans("root123", table_name: table_name)
502+
503+
assert length(all_descendants) == 5
504+
505+
span_ids = Enum.map(all_descendants, & &1.span_id)
506+
assert "child1" in span_ids
507+
assert "child2" in span_ids
508+
assert "grandchild1" in span_ids
509+
assert "grandchild2" in span_ids
510+
assert "grandchild3" in span_ids
511+
512+
start_times = Enum.map(all_descendants, & &1.start_time)
513+
assert start_times == Enum.sort(start_times)
514+
end
515+
516+
@tag span_storage: true
517+
test "retrieves deep nested hierarchies correctly", %{table_name: table_name} do
518+
spans = [
519+
%SpanRecord{
520+
span_id: "root",
521+
parent_span_id: nil,
522+
trace_id: "trace123",
523+
name: "root_span",
524+
start_time: 1000,
525+
end_time: 2000
526+
},
527+
%SpanRecord{
528+
span_id: "child",
529+
parent_span_id: "root",
530+
trace_id: "trace123",
531+
name: "child_span",
532+
start_time: 1100,
533+
end_time: 1900
534+
},
535+
%SpanRecord{
536+
span_id: "grandchild",
537+
parent_span_id: "child",
538+
trace_id: "trace123",
539+
name: "grandchild_span",
540+
start_time: 1200,
541+
end_time: 1800
542+
},
543+
%SpanRecord{
544+
span_id: "great_grandchild",
545+
parent_span_id: "grandchild",
546+
trace_id: "trace123",
547+
name: "great_grandchild_span",
548+
start_time: 1300,
549+
end_time: 1700
550+
}
551+
]
552+
553+
Enum.each(spans, &SpanStorage.store_span(&1, table_name: table_name))
554+
555+
all_descendants = SpanStorage.get_child_spans("root", table_name: table_name)
556+
assert length(all_descendants) == 3
557+
558+
span_ids = Enum.map(all_descendants, & &1.span_id)
559+
assert "child" in span_ids
560+
assert "grandchild" in span_ids
561+
assert "great_grandchild" in span_ids
562+
563+
child_descendants = SpanStorage.get_child_spans("child", table_name: table_name)
564+
assert length(child_descendants) == 2
565+
566+
child_span_ids = Enum.map(child_descendants, & &1.span_id)
567+
assert "grandchild" in child_span_ids
568+
assert "great_grandchild" in child_span_ids
569+
570+
grandchild_descendants = SpanStorage.get_child_spans("grandchild", table_name: table_name)
571+
assert length(grandchild_descendants) == 1
572+
assert hd(grandchild_descendants).span_id == "great_grandchild"
573+
end
574+
575+
@tag span_storage: true
576+
test "handles multiple disconnected subtrees correctly", %{table_name: table_name} do
577+
spans = [
578+
%SpanRecord{
579+
span_id: "branch1",
580+
parent_span_id: "root",
581+
trace_id: "trace123",
582+
name: "branch1_span",
583+
start_time: 1100,
584+
end_time: 1500
585+
},
586+
%SpanRecord{
587+
span_id: "leaf1",
588+
parent_span_id: "branch1",
589+
trace_id: "trace123",
590+
name: "leaf1_span",
591+
start_time: 1150,
592+
end_time: 1250
593+
},
594+
%SpanRecord{
595+
span_id: "leaf2",
596+
parent_span_id: "branch1",
597+
trace_id: "trace123",
598+
name: "leaf2_span",
599+
start_time: 1300,
600+
end_time: 1400
601+
},
602+
%SpanRecord{
603+
span_id: "branch2",
604+
parent_span_id: "root",
605+
trace_id: "trace123",
606+
name: "branch2_span",
607+
start_time: 1600,
608+
end_time: 1900
609+
},
610+
%SpanRecord{
611+
span_id: "leaf3",
612+
parent_span_id: "branch2",
613+
trace_id: "trace123",
614+
name: "leaf3_span",
615+
start_time: 1650,
616+
end_time: 1750
617+
}
618+
]
619+
620+
Enum.each(spans, &SpanStorage.store_span(&1, table_name: table_name))
621+
622+
all_descendants = SpanStorage.get_child_spans("root", table_name: table_name)
623+
assert length(all_descendants) == 5
624+
625+
span_ids = Enum.map(all_descendants, & &1.span_id)
626+
assert "branch1" in span_ids
627+
assert "branch2" in span_ids
628+
assert "leaf1" in span_ids
629+
assert "leaf2" in span_ids
630+
assert "leaf3" in span_ids
631+
632+
start_times = Enum.map(all_descendants, & &1.start_time)
633+
assert start_times == [1100, 1150, 1300, 1600, 1650]
634+
end
635+
end
636+
437637
describe "cleanup" do
438638
@tag span_storage: [cleanup_interval: 100]
439639
test "cleanup respects span TTL precisely", %{table_name: table_name} do

test_integrations/phoenix_app/lib/phoenix_app_web/controllers/page_controller.ex

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule PhoenixAppWeb.PageController do
33

44
require OpenTelemetry.Tracer, as: Tracer
55

6-
alias PhoenixApp.{Repo, User}
6+
alias PhoenixApp.{Repo, Accounts.User}
77

88
def home(conn, _params) do
99
render(conn, :home, layout: false)
@@ -26,4 +26,26 @@ defmodule PhoenixAppWeb.PageController do
2626

2727
render(conn, :home, layout: false)
2828
end
29+
30+
def nested_spans(conn, _params) do
31+
Tracer.with_span "root_span" do
32+
Tracer.with_span "child_span_1" do
33+
Tracer.with_span "grandchild_span_1" do
34+
:timer.sleep(50)
35+
end
36+
37+
Tracer.with_span "grandchild_span_2" do
38+
Repo.all(User) |> Enum.count()
39+
end
40+
end
41+
42+
Tracer.with_span "child_span_2" do
43+
Tracer.with_span "grandchild_span_3" do
44+
:timer.sleep(30)
45+
end
46+
end
47+
end
48+
49+
render(conn, :home, layout: false)
50+
end
2951
end

test_integrations/phoenix_app/lib/phoenix_app_web/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ defmodule PhoenixAppWeb.Router do
2020
get "/", PageController, :home
2121
get "/exception", PageController, :exception
2222
get "/transaction", PageController, :transaction
23+
get "/nested-spans", PageController, :nested_spans
2324

2425
live "/test-worker", TestWorkerLive
2526

0 commit comments

Comments
 (0)