Skip to content

Commit 8105748

Browse files
committed
refactor: improve event handling tests with realistic UUIDs and updated attributes
Enhanced the OpenTelemetry event handler tests by replacing hardcoded values with realistic UUIDs for event IDs, stream IDs, causation, and correlation IDs. Updated attribute assertions to reflect the new structure and naming conventions for projectors. Improved error handling tests to simulate realistic scenarios, ensuring better coverage and accuracy in telemetry data. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 959e8db commit 8105748

8 files changed

Lines changed: 180 additions & 286 deletions

File tree

‎guides/howtos/setting-up-opentelemetry-tracing.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,20 @@ Choose one of the following span relationship modes when calling `setup/1`:
4444

4545
```elixir
4646
# Create span links to the original command dispatch (default)
47-
Commanded.OpenTelemetry.setup(tracing: [span_relationship: :link])
47+
Commanded.OpenTelemetry.setup(event_handler: [span_relationship: :link])
4848

4949
# Make event handler spans children of the command span
50-
Commanded.OpenTelemetry.setup(tracing: [span_relationship: :child])
50+
Commanded.OpenTelemetry.setup(event_handler: [span_relationship: :child])
5151

5252
# No span propagation between commands and event handlers
53-
Commanded.OpenTelemetry.setup(tracing: [span_relationship: :none])
53+
Commanded.OpenTelemetry.setup(event_handler: [span_relationship: :none])
5454
```
5555

5656
Note: `setup/1` should only be called once during application startup.
5757

5858
## Disable Event Handler Tracing
5959

6060
```elixir
61-
Commanded.OpenTelemetry.setup(tracing: [event_handler: false])
61+
Commanded.OpenTelemetry.setup(event_handler: false)
6262
```
6363

‎lib/commanded/middleware/trace_context_propagator.ex‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ if Code.ensure_loaded?(:otel_propagator_text_map) do
6161
end
6262
end
6363

64-
@spec maybe_assign(%Pipeline{}, String.t(), {String.t(), String.t()} | nil) :: %Pipeline{}
6564
defp maybe_assign(pipeline, _key, nil), do: pipeline
6665

6766
defp maybe_assign(pipeline, key, {_, value}),

‎lib/commanded/opentelemetry.ex‎

Lines changed: 44 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -30,46 +30,44 @@ defmodule Commanded.OpenTelemetry do
3030
3131
# ... your command routes
3232
end
33+
34+
## Types
35+
36+
See `t:span_relationship/0` for available span relationship modes.
3337
"""
3438

3539
alias Commanded.OpenTelemetry.EventHandler
3640

37-
@schema [
38-
tracer: [
39-
type: :keyword_list,
40-
default: [],
41-
doc: "Tracer configuration",
42-
keys: [
43-
name: [
44-
type: :atom,
45-
default: :commanded,
46-
doc: "Application name for OpenTelemetry tracer registration"
47-
]
48-
]
49-
],
50-
tracing: [
51-
type: :keyword_list,
52-
default: [],
53-
doc: "Tracing configuration",
54-
keys: [
55-
event_handler: [
56-
type: :boolean,
57-
default: true,
58-
doc: "Trace event handling operations"
59-
],
60-
span_relationship: [
61-
type: {:in, [:link, :child, :none]},
62-
default: :link,
63-
doc: """
64-
How event handlers relate to command spans:
65-
* `:link` - Create span links to the original command dispatch (default)
66-
* `:child` - Attach event handler spans as children of command span
67-
* `:none` - No span propagation between commands and event handlers
68-
"""
69-
]
70-
]
71-
]
72-
]
41+
@typedoc """
42+
Determines how event handler spans relate to command dispatch spans.
43+
44+
* `:link` - Create span links to the original command dispatch (default).
45+
Best for event-driven architectures where events are processed independently.
46+
* `:child` - Attach event handler spans as children of the command span.
47+
Best when you want a single trace tree for the entire command lifecycle.
48+
* `:none` - No span propagation between commands and event handlers.
49+
Best when events should start fresh traces.
50+
"""
51+
@type span_relationship :: :link | :child | :none
52+
53+
@nimble_schema NimbleOptions.new!(
54+
event_handler: [
55+
type:
56+
{:or,
57+
[
58+
{:in, [:disabled]},
59+
keyword_list: [
60+
span_relationship: [
61+
type: {:in, [:link, :child, :none]},
62+
type_doc: "`t:span_relationship/0`",
63+
default: :link
64+
]
65+
]
66+
]},
67+
default: [],
68+
doc: "Event handler tracing configuration. Use `:disabled` to disable."
69+
]
70+
)
7371

7472
@doc """
7573
Set up OpenTelemetry tracing for Commanded.
@@ -78,52 +76,27 @@ defmodule Commanded.OpenTelemetry do
7876
7977
## Options
8078
81-
#{NimbleOptions.docs(@schema)}
79+
#{NimbleOptions.docs(@nimble_schema)}
8280
8381
## Examples
8482
85-
# Default setup
83+
# Default setup (uses :link relationship)
8684
Commanded.OpenTelemetry.setup()
8785
88-
# With custom tracer name
89-
Commanded.OpenTelemetry.setup(tracer: [name: :my_app])
90-
9186
# Disable event handler tracing
92-
Commanded.OpenTelemetry.setup(tracing: [event_handler: false])
87+
Commanded.OpenTelemetry.setup(event_handler: :disabled)
9388
9489
# Use parent-child relationships for event handlers
95-
Commanded.OpenTelemetry.setup(tracing: [span_relationship: :child])
90+
Commanded.OpenTelemetry.setup(event_handler: [span_relationship: :child])
9691
9792
"""
98-
@spec setup(keyword()) :: :ok | {:error, :already_exists}
93+
@spec setup(keyword()) :: :ok
9994
def setup(opts \\ []) do
100-
opts = NimbleOptions.validate!(opts, @schema)
101-
102-
if opts[:tracing][:event_handler] do
103-
case EventHandler.setup(
104-
tracer_name: opts[:tracer][:name],
105-
span_relationship: opts[:tracing][:span_relationship]
106-
) do
107-
:ok -> :ok
108-
{:error, :already_exists} = error -> error
109-
end
110-
else
111-
:ok
112-
end
113-
end
95+
opts = NimbleOptions.validate!(opts, @nimble_schema)
11496

115-
@doc """
116-
Detach all OpenTelemetry telemetry handlers.
117-
118-
Use this to clean up handlers, for example in tests or when reconfiguring.
119-
120-
## Example
121-
122-
Commanded.OpenTelemetry.detach()
123-
124-
"""
125-
@spec detach() :: :ok
126-
def detach do
127-
EventHandler.detach()
97+
case opts[:event_handler] do
98+
:disabled -> :ok
99+
config -> EventHandler.setup(config)
100+
end
128101
end
129102
end

‎lib/commanded/opentelemetry/event_handler.ex‎

Lines changed: 25 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,17 @@ defmodule Commanded.OpenTelemetry.EventHandler do
77
alias OpenTelemetry.SemConv.Incubating.MessagingAttributes
88
alias OpenTelemetry.Span
99

10-
@default_tracer_id __MODULE__
10+
@tracer_id __MODULE__
1111

12-
@typedoc "Span relationship configuration"
13-
@type span_relationship :: :link | :child | :none
14-
15-
@typedoc "Setup options"
16-
@type setup_opts :: [tracer_name: atom(), span_relationship: span_relationship()]
17-
18-
@typedoc "Handler configuration stored in telemetry handler config"
19-
@type handler_config :: %{tracer_id: atom(), span_relationship: span_relationship()}
20-
21-
@doc false
22-
@spec setup(setup_opts()) :: :ok | {:error, :already_exists}
2312
def setup(opts \\ []) do
24-
tracer_name = opts[:tracer_name] || @default_tracer_id
25-
span_relationship = opts[:span_relationship] || :link
26-
config = %{tracer_id: tracer_name, span_relationship: span_relationship}
13+
span_relationship = Keyword.get(opts, :span_relationship, :link)
14+
config = %{span_relationship: span_relationship}
15+
16+
:ok = attach_handle_handlers(config)
17+
:ok = attach_batch_handlers(config)
18+
end
2719

28-
# Single event handlers
20+
defp attach_handle_handlers(config) do
2921
:telemetry.attach_many(
3022
{__MODULE__, :handle},
3123
[
@@ -36,8 +28,9 @@ defmodule Commanded.OpenTelemetry.EventHandler do
3628
&__MODULE__.handle_telemetry_event/4,
3729
config
3830
)
31+
end
3932

40-
# Batch event handlers
33+
defp attach_batch_handlers(config) do
4134
:telemetry.attach_many(
4235
{__MODULE__, :batch},
4336
[
@@ -50,22 +43,6 @@ defmodule Commanded.OpenTelemetry.EventHandler do
5043
)
5144
end
5245

53-
@doc false
54-
@spec detach() :: :ok
55-
def detach do
56-
:telemetry.detach({__MODULE__, :handle})
57-
:telemetry.detach({__MODULE__, :batch})
58-
:ok
59-
end
60-
61-
# Single event handler dispatcher
62-
@doc false
63-
@spec handle_telemetry_event(
64-
:telemetry.event_name(),
65-
:telemetry.event_measurements(),
66-
:telemetry.event_metadata(),
67-
handler_config()
68-
) :: OpenTelemetry.span_ctx() | :ok
6946
def handle_telemetry_event([:commanded, :event, :handle, :start], measurements, meta, config),
7047
do: handle_start(measurements, meta, config)
7148

@@ -80,14 +57,6 @@ defmodule Commanded.OpenTelemetry.EventHandler do
8057
),
8158
do: handle_exception(measurements, meta, config)
8259

83-
# Batch event handler dispatcher
84-
@doc false
85-
@spec batch_telemetry_event(
86-
:telemetry.event_name(),
87-
:telemetry.event_measurements(),
88-
:telemetry.event_metadata(),
89-
handler_config()
90-
) :: OpenTelemetry.span_ctx() | :ok
9160
def batch_telemetry_event([:commanded, :event, :batch, :start], measurements, meta, config),
9261
do: batch_start(measurements, meta, config)
9362

@@ -158,8 +127,6 @@ defmodule Commanded.OpenTelemetry.EventHandler do
158127
# TODO: Add last_seen_event attribute when available in Commanded telemetry
159128
# "event.last_seen": meta.last_seen_event
160129

161-
tracer_id = config.tracer_id
162-
163130
span_opts = %{
164131
kind: :consumer,
165132
attributes: attributes
@@ -173,31 +140,29 @@ defmodule Commanded.OpenTelemetry.EventHandler do
173140
end
174141

175142
OpentelemetryTelemetry.start_telemetry_span(
176-
tracer_id,
143+
@tracer_id,
177144
"commanded.event.handle",
178145
meta,
179146
span_opts
180147
)
181148
end
182149

183-
defp handle_stop(_measurements, meta, config) do
184-
tracer_id = config.tracer_id
185-
ctx = OpentelemetryTelemetry.set_current_telemetry_span(tracer_id, meta)
150+
defp handle_stop(_measurements, meta, _config) do
151+
ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta)
186152

187153
if error = meta[:error] do
188154
Span.set_status(ctx, OpenTelemetry.status(:error, inspect(error)))
189155
end
190156

191-
OpentelemetryTelemetry.end_telemetry_span(tracer_id, meta)
157+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta)
192158
end
193159

194160
defp handle_exception(
195161
_measurements,
196162
%{kind: kind, reason: reason, stacktrace: stacktrace} = meta,
197-
config
163+
_config
198164
) do
199-
tracer_id = config.tracer_id
200-
ctx = OpentelemetryTelemetry.set_current_telemetry_span(tracer_id, meta)
165+
ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta)
201166

202167
# Add exception kind as attribute for better debugging
203168
Span.set_attribute(ctx, :"exception.kind", kind)
@@ -209,12 +174,12 @@ defmodule Commanded.OpenTelemetry.EventHandler do
209174
Span.record_exception(ctx, exception, stacktrace)
210175
Span.set_status(ctx, OpenTelemetry.status(:error, inspect(reason)))
211176

212-
OpentelemetryTelemetry.end_telemetry_span(tracer_id, meta)
177+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta)
213178
end
214179

215180
# Batch event handlers
216181

217-
defp batch_start(_measurements, meta, config) do
182+
defp batch_start(_measurements, meta, _config) do
218183
# Note: Batch telemetry metadata does not include individual recorded_events,
219184
# only first_event_id, last_event_id, and event_count. Therefore, we cannot
220185
# create span links to individual command dispatch traces for batch events.
@@ -247,39 +212,35 @@ defmodule Commanded.OpenTelemetry.EventHandler do
247212
{CommandedAttributes.commanded_batch_last_event_id(), meta.last_event_id}
248213
]
249214

250-
tracer_id = config.tracer_id
251-
252215
span_opts = %{
253216
kind: :consumer,
254217
attributes: attributes
255218
}
256219

257220
OpentelemetryTelemetry.start_telemetry_span(
258-
tracer_id,
221+
@tracer_id,
259222
"commanded.event.batch",
260223
meta,
261224
span_opts
262225
)
263226
end
264227

265-
defp batch_stop(_measurements, meta, config) do
266-
tracer_id = config.tracer_id
267-
ctx = OpentelemetryTelemetry.set_current_telemetry_span(tracer_id, meta)
228+
defp batch_stop(_measurements, meta, _config) do
229+
ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta)
268230

269231
if error = meta[:error] do
270232
Span.set_status(ctx, OpenTelemetry.status(:error, inspect(error)))
271233
end
272234

273-
OpentelemetryTelemetry.end_telemetry_span(tracer_id, meta)
235+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta)
274236
end
275237

276238
defp batch_exception(
277239
_measurements,
278240
%{kind: kind, reason: reason, stacktrace: stacktrace} = meta,
279-
config
241+
_config
280242
) do
281-
tracer_id = config.tracer_id
282-
ctx = OpentelemetryTelemetry.set_current_telemetry_span(tracer_id, meta)
243+
ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta)
283244

284245
# Add exception kind as attribute for better debugging
285246
Span.set_attribute(ctx, :"exception.kind", kind)
@@ -291,6 +252,6 @@ defmodule Commanded.OpenTelemetry.EventHandler do
291252
Span.record_exception(ctx, exception, stacktrace)
292253
Span.set_status(ctx, OpenTelemetry.status(:error, inspect(reason)))
293254

294-
OpentelemetryTelemetry.end_telemetry_span(tracer_id, meta)
255+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta)
295256
end
296257
end

0 commit comments

Comments
 (0)