Skip to content

Commit e9e3f95

Browse files
authored
[DEVEX-2701]: Teleplug traces not showing up if traces are propagated from outside (#188)
1 parent c803bf5 commit e9e3f95

File tree

5 files changed

+149
-9
lines changed

5 files changed

+149
-9
lines changed

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ and this project adheres to
1010

1111
---
1212

13+
## [2.1.0] - 2025-09-25
14+
15+
### Added
16+
17+
- `trace_propagation` configuration option, for specifying how traces should be propagated(`:as_parent`(default), `:as_link` and `disabled`)
18+
19+
---
20+
1321
## [2.0.1] - 2025-08-04
1422

1523
### Fixed
@@ -78,7 +86,9 @@ and this project adheres to
7886
upgrade to OpenTelemetry API 1.1.0
7987

8088

81-
[Unreleased]: https://github.com/primait/teleplug/compare/2.0.1...HEAD
89+
90+
[Unreleased]: https://github.com/primait/teleplug/compare/2.1.0...HEAD
91+
[2.1.0]: https://github.com/primait/teleplug/compare/2.0.1...2.1.0
8292
[2.0.1]: https://github.com/primait/teleplug/compare/2.0.0...2.0.1
8393
[2.0.0]: https://github.com/primait/teleplug/compare/1.1.3...2.0.0
8494
[1.1.3]: https://github.com/primait/teleplug/compare/1.1.2...1.1.3

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,18 @@ be found at [https://hexdocs.pm/teleplug](https://hexdocs.pm/teleplug).
3030
In your pipeline add
3131

3232
```
33-
plug TelePlug
33+
plug Teleplug
3434
```
3535

36+
There are some options you can pass, for example
37+
38+
```
39+
plug Teleplug,
40+
trace_propagation: :as_link
41+
```
42+
43+
see the module documentation for details
44+
3645
## Copyright and License
3746

3847
Copyright (c) 2020 Prima.it

lib/teleplug.ex

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,79 @@ defmodule Teleplug do
4242

4343
defdelegate setup, to: Teleplug.Instrumentation
4444

45+
@typedoc """
46+
Configuration options for Teleplug
47+
* trace_propagation_opt(default: `:as_parent`) - configure how trace propagation works.
48+
"""
49+
@type opts() :: [
50+
trace_propagation: trace_propagation_opt()
51+
]
52+
53+
@typedoc "
54+
How to handle trace propagation headers:
55+
- `:as_parent` set the propagated trace span as a parent for the request handler span.
56+
Note that if the parent span doesn't exist your trace might be dropped by the opentelemetry collector.
57+
If that behaviour is undesirable(eg. when using a public endpoint) you should use the `:as_link` option.
58+
- `:as_link` create a [link](https://opentelemetry.io/docs/concepts/signals/traces/#span-links) between the propagated span and the request handler span.
59+
- `:disabled` disable trace propagation.
60+
"
61+
@type trace_propagation_opt() :: :as_parent | :as_link | :disabled
62+
4563
@impl true
46-
def init(opts), do: opts
64+
@spec init(opts() | nil) :: opts()
65+
def init(opts) when is_list(opts) do
66+
# validate and set defaults for all options
67+
{trace_propagation, opts} = Keyword.pop(opts, :trace_propagation, :as_parent)
68+
69+
if trace_propagation not in [:as_parent, :as_link, :disabled] do
70+
raise ArgumentError,
71+
message: "invalid trace_propagation configuration value: #{trace_propagation}"
72+
end
73+
74+
unused_keys = Keyword.keys(opts)
75+
76+
if unused_keys != [] do
77+
raise ArgumentError, message: "Unknown teleplug options: #{unused_keys}"
78+
end
79+
80+
[
81+
trace_propagation: trace_propagation
82+
]
83+
end
84+
85+
def init(opts) when is_nil(opts), do: init([])
4786

4887
@impl true
49-
def call(conn, _opts) do
50-
:otel_propagator_text_map.extract(conn.req_headers)
88+
@spec call(Plug.Conn.t(), opts()) :: Plug.Conn.t()
89+
def call(conn, opts) do
90+
trace_propagation = Keyword.fetch!(opts, :trace_propagation)
5191

5292
attributes =
5393
http_common_attributes(conn) ++
5494
http_server_attributes(conn) ++
5595
network_attributes(conn)
5696

57-
parent_ctx = Tracer.current_span_ctx()
97+
if trace_propagation == :as_parent do
98+
:otel_propagator_text_map.extract(conn.req_headers)
99+
end
58100

59-
new_ctx = conn |> span_name() |> Tracer.start_span(%{kind: :server, attributes: attributes})
101+
links =
102+
case trace_propagation do
103+
:as_link ->
104+
OpenTelemetry.Ctx.new()
105+
|> :otel_propagator_text_map.extract_to(conn.req_headers)
106+
|> OpenTelemetry.Tracer.current_span_ctx()
107+
|> OpenTelemetry.link()
108+
|> List.wrap()
109+
110+
_ ->
111+
[]
112+
end
113+
114+
new_ctx =
115+
conn
116+
|> span_name()
117+
|> Tracer.start_span(%{kind: :server, attributes: attributes, links: links})
60118

61119
Tracer.set_current_span(new_ctx)
62120

@@ -74,7 +132,6 @@ defmodule Teleplug do
74132
RequestMonitor.end_span(request_monitor_ref)
75133
end
76134

77-
Tracer.set_current_span(parent_ctx)
78135
conn
79136
end)
80137
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Teleplug.MixProject do
22
use Mix.Project
33

44
@source_url "https://github.com/primait/teleplug"
5-
@version "2.0.1"
5+
@version "2.1.0"
66

77
def project do
88
[

test/teleplug_test.exs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,70 @@ defmodule TeleplugTest do
104104
} = attributes
105105
end
106106

107+
test "trace propagation as parent by default" do
108+
opts = Teleplug.init([])
109+
110+
{propagated_span_id, propagation_headers} =
111+
Tracer.with_span "propagated" do
112+
span_id =
113+
OpenTelemetry.Tracer.current_span_ctx()
114+
|> OpenTelemetry.Span.span_id()
115+
116+
headers = :otel_propagator_text_map.inject([])
117+
{span_id, headers}
118+
end
119+
120+
:get
121+
|> conn("/")
122+
|> Plug.Conn.merge_req_headers(propagation_headers)
123+
|> Teleplug.call(opts)
124+
|> Plug.Conn.send_resp(200, "ok")
125+
126+
assert_receive {:span, span(parent_span_id: ^propagated_span_id, name: "GET /")}, 1_000
127+
end
128+
129+
test "trace propagation as link" do
130+
opts = Teleplug.init(trace_propagation: :as_link)
131+
132+
{propagated_span_id, propagation_headers} =
133+
Tracer.with_span "propagated" do
134+
span_id =
135+
OpenTelemetry.Tracer.current_span_ctx()
136+
|> OpenTelemetry.Span.span_id()
137+
138+
headers = :otel_propagator_text_map.inject([])
139+
{span_id, headers}
140+
end
141+
142+
:get
143+
|> conn("/")
144+
|> Plug.Conn.merge_req_headers(propagation_headers)
145+
|> Teleplug.call(opts)
146+
|> Plug.Conn.send_resp(200, "ok")
147+
148+
assert_receive {:span, span(links: links, name: "GET /")}, 1_000
149+
assert {:links, _, _, _, _, [link]} = links
150+
assert {:link, _, ^propagated_span_id, _, _} = link
151+
end
152+
153+
test "trace propagation disabled" do
154+
opts = Teleplug.init(trace_propagation: :disabled)
155+
156+
propagation_headers =
157+
Tracer.with_span "propagated" do
158+
:otel_propagator_text_map.inject([])
159+
end
160+
161+
:get
162+
|> conn("/")
163+
|> Plug.Conn.merge_req_headers(propagation_headers)
164+
|> Teleplug.call(opts)
165+
|> Plug.Conn.send_resp(200, "ok")
166+
167+
assert_receive {:span, span(parent_span_id: :undefined, links: links, name: "GET /")}, 1_000
168+
assert {:links, _, _, _, _, []} = links
169+
end
170+
107171
def flush_mailbox do
108172
receive do
109173
_ -> flush_mailbox()

0 commit comments

Comments
 (0)