Skip to content

Commit 2574d64

Browse files
author
Tristan Sloughter
authored
Merge pull request #362 from tsloughter/api-readme
Add docs for the tracing api macros to the readme
2 parents 01e4944 + 7e45530 commit 2574d64

File tree

16 files changed

+296
-65
lines changed

16 files changed

+296
-65
lines changed

.github/workflows/elixir.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
- name: Compile
5757
run: rebar3 as test compile
5858
- name: ExUnit
59-
run: mix test test/otel_tests.exs
59+
run: mix test --no-start test/otel_tests.exs
6060

6161
api_tests:
6262
runs-on: ${{ matrix.os }}

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
- [Simpler configuration of span processors](https://github.com/open-telemetry/opentelemetry-erlang/pull/357)
1515

16+
#### Fixed
17+
18+
- Span Status: Ignore status changes that don't follow the [define precedence in
19+
the spec](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#set-status)
20+
1621
### [Zipkin Exporter]
1722

1823
#### Fixed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ slack](https://slack.cncf.io/). Please join us for more informal discussions.
3939
You can also find us in the #opentelemetry channel on [Elixir
4040
Slack](https://elixir-slackin.herokuapp.com/).
4141

42+
## Getting Started
43+
44+
You can find a getting started guide on [opentelemetry.io](https://opentelemetry.io/docs/instrumentation/erlang/getting-started/).
45+
46+
To start capturing distributed traces from your application it first needs to be
47+
instrumented. The easiest way to do this is by using an instrumentation library,
48+
there are a number of [officially supported instrumentation
49+
libraries](https://github.com/open-telemetry/opentelemetry-erlang-contrib) for
50+
popular Erlang and Elixir libraries and frameworks.
51+
4252
## Design
4353

4454
The [OpenTelemetry

apps/opentelemetry/src/otel_span_ets.erl

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
4040
-include("otel_span.hrl").
4141
-include("otel_span_ets.hrl").
42+
-include_lib("stdlib/include/ms_transform.hrl").
4243

4344
-record(state, {}).
4445

@@ -136,8 +137,24 @@ add_events(#span_ctx{span_id=SpanId}, NewEvents) ->
136137
end.
137138

138139
-spec set_status(opentelemetry:span_ctx(), opentelemetry:status()) -> boolean().
139-
set_status(#span_ctx{span_id=SpanId}, Status) ->
140-
ets:update_element(?SPAN_TAB, SpanId, {#span.status, Status}).
140+
set_status(#span_ctx{span_id=SpanId}, Status=#status{code=NewCode}) ->
141+
MS = ets:fun2ms(fun(Span=#span{span_id=Id,
142+
status=#status{code=?OTEL_STATUS_ERROR}}) when Id =:= SpanId ,
143+
NewCode =:= ?OTEL_STATUS_OK ->
144+
%% can only set status to OK if it has been set to ERROR before
145+
Span#span{status=#status{code=?OTEL_STATUS_OK}};
146+
(Span=#span{span_id=Id,
147+
status=#status{code=?OTEL_STATUS_UNSET}}) when Id =:= SpanId ->
148+
%% if UNSET then the status can be updated to OK or ERROR
149+
Span#span{status=Status};
150+
(Span=#span{span_id=Id,
151+
status=undefined}) when Id =:= SpanId ->
152+
%% if undefined then the status can be updated to anything
153+
Span#span{status=Status}
154+
end),
155+
ets:select_replace(?SPAN_TAB, MS) =:= 1;
156+
set_status(_, _) ->
157+
false.
141158

142159
-spec update_name(opentelemetry:span_ctx(), opentelemetry:span_name()) -> boolean().
143160
update_name(#span_ctx{span_id=SpanId}, Name) ->

apps/opentelemetry/test/opentelemetry_SUITE.erl

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,15 +373,33 @@ update_span_data(Config) ->
373373
tracestate=[]}],
374374

375375
SpanCtx1=#span_ctx{trace_id=TraceId,
376-
span_id=SpanId} = ?start_span(<<"span-1">>, #{links => Links}),
376+
span_id=SpanId,
377+
is_recording=true} = ?start_span(<<"span-1">>, #{links => Links}),
377378
?set_current_span(SpanCtx1),
378379
?set_attribute(<<"key-1">>, <<"value-1">>),
379380

380381
Events = opentelemetry:events([{opentelemetry:timestamp(),
381382
<<"event-name">>, []}]),
382-
Status = opentelemetry:status(0, <<"status">>),
383-
384-
otel_span:set_status(SpanCtx1, Status),
383+
ErrorStatus = opentelemetry:status(?OTEL_STATUS_ERROR, <<"status">>),
384+
?assertMatch(#status{code=?OTEL_STATUS_ERROR,
385+
message = <<"status">>}, ErrorStatus),
386+
387+
OkStatus = opentelemetry:status(?OTEL_STATUS_OK, <<"will be ignored">>),
388+
?assertMatch(#status{code=?OTEL_STATUS_OK,
389+
message = <<>>}, OkStatus),
390+
?assertEqual(OkStatus, opentelemetry:status(?OTEL_STATUS_OK)),
391+
392+
UnsetStatus = opentelemetry:status(?OTEL_STATUS_UNSET, <<"will be ignored">>),
393+
?assertMatch(#status{code=?OTEL_STATUS_UNSET,
394+
message = <<>>}, UnsetStatus),
395+
?assertEqual(UnsetStatus, opentelemetry:status(?OTEL_STATUS_UNSET)),
396+
397+
?assert(otel_span:set_status(SpanCtx1, OkStatus)),
398+
%% spec does not allow setting status to error/unset after it is ok
399+
?assertNot(otel_span:set_status(SpanCtx1, ErrorStatus)),
400+
?assertNot(otel_span:set_status(SpanCtx1, ?OTEL_STATUS_ERROR)),
401+
%% %% returns false if called with something that isn't a status record
402+
?assertNot(otel_span:set_status(SpanCtx1, notastatus)),
385403

386404
%% returning not false means it successfully called the SDK
387405
?assertNotEqual(false, otel_span:add_event(SpanCtx1, event_1, #{<<"attr-1">> => <<"attr-value-1">>})),
@@ -394,7 +412,7 @@ update_span_data(Config) ->
394412
links=L,
395413
events=E}] = ?UNTIL_NOT_EQUAL([], ets:match_object(Tid, #span{trace_id=TraceId,
396414
span_id=SpanId,
397-
status=Status,
415+
status=OkStatus,
398416
_='_'})),
399417

400418

apps/opentelemetry_api/README.md

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,180 @@ some_fun() ->
3030

3131
``` elixir
3232
require OpenTelemetry.Tracer
33-
require OpenTelemetry.Span
3433

3534
def some_fun() do
3635
OpenTelemetry.Tracer.with_span "some-span" do
3736
...
38-
OpenTelemetry.Span.set_attribute("key", "value")
37+
OpenTelemetry.Tracer.set_attribute("key", "value")
3938
...
4039
end
4140
end
4241
```
4342

43+
### Tracing API
44+
45+
The macros and functions available for Elixir in `OpenTelemetry.Tracer` and the
46+
Erlang macros in `otel_tracer.hrl` are the best way to work with Spans. They
47+
will automatically use the Tracer named for the Application the module using the
48+
macro is in. For example, the Spans created in
49+
[opentelemetry_oban](https://hex.pm/packages/opentelemetry_oban) use the
50+
`with_span` macro resulting in the Span being created with the
51+
`opentelemetry_oban` named Tracer and associated with the [Instrumentation
52+
Library](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/glossary.md#instrumentation-library)
53+
of the same name and version of the Tracer -- the version also matches the
54+
`opentelemetry_oban` Application version.
55+
56+
#### Context
57+
58+
[Context](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/context/context.md) is used to pass values associated with the current [execution
59+
unit](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/glossary.md#execution-unit).
60+
At this time the only values kept in the Context by this OpenTelemetry library
61+
are the [Span
62+
Context](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#spancontext)
63+
for the currently active Span and the
64+
[Baggage](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/baggage/api.md)
65+
66+
When a Context variable is not an explicit argument in the API macros or
67+
functions the Context from the [process
68+
dictionary](https://www.erlang.org/doc/reference_manual/processes.html#process-dictionary)
69+
is used. If no Context is found in the current process's pdict then one is
70+
created.
71+
72+
#### Starting and Ending Spans
73+
74+
A Span represents a single operation in a Trace. It has a start and end time,
75+
can have a single parent and one or more children. The easiest way to create
76+
Spans is to wrap the operation you want a Span to represent in the `with_span`
77+
macro. The macro handles getting a
78+
[Tracer](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#tracer)
79+
associated with the OTP Application the module is in, starting the Span, setting
80+
it as the currently active Span in the Context stored in the process dictionary
81+
and ending the Span when the `Fun` or body of the Elixir macro finish, even if
82+
an exception is thrown -- however, the exception is not caught, so it does not
83+
change how user code should deal with raised exceptions. After the Span is
84+
ended the Context in the process dictionary is reset to its value before the
85+
newly started Span was set as the active Span. This handling of the active Span
86+
in the process dictionary ensures proper lineage of Spans is kept when starting
87+
and ending child Spans.
88+
89+
``` erlang
90+
?with_span(SpanName, StartOpts, Fun)
91+
```
92+
93+
``` elixir
94+
OpenTelemetry.Tracer.with_span name, start_opts do
95+
...
96+
end
97+
```
98+
99+
`StartOpts`/`start_opts` is a map of [Span creation options](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#span-creation):
100+
101+
- `kind`:
102+
[SpanKind](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#spankind)
103+
defines the relationship between the Span, its parents, and its children in a
104+
Trace. Possible values: `internal`, `server`, `client`, `producer` and
105+
`consumer`. Defaults to `internal` if not specified.
106+
- `attributes`: See
107+
[Attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/common/common.md#attributes)
108+
for details about Attributes. Default is an empty list of attributes.
109+
- `links`: List of [Links](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/overview.md#links-between-spans) to causally related Spans from the same or a different Trace.
110+
- `start_time`: The start time of the Span operation. Defaults to the current
111+
time. The option should only be set if the start of the operation described by
112+
the Span has already passed.
113+
114+
current_span_ctx(ctx)
115+
116+
set_current_span(span_ctx)
117+
118+
When using `start_span` instead of `with_span` there must be a corresponding
119+
call to the [end Span
120+
API](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#end)
121+
to signal that the operation described by the Span has ended. `end_span`
122+
optionally takes a timestamp to use as the end time of the Span.
123+
124+
``` erlang
125+
?end_span()
126+
?end_span(Timestamp)
127+
```
128+
129+
``` elixir
130+
OpenTelemetry.Tracer.end_span(timestamp \\ :undefined)
131+
```
132+
133+
#### Setting Attributes
134+
135+
[Setting
136+
Attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#set-attributes)
137+
can be done with a single key and value passed to `set_attribute` or through a
138+
map of
139+
[Attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/common/common.md#attributes)
140+
all at once. Setting an attribute with a key that already exists in the Span's
141+
map of attributes will result in that key's value being overwritten.
142+
143+
``` erlang
144+
?set_attribute(Key, Value)
145+
?set_attributes(Attributes)
146+
```
147+
148+
``` elixir
149+
OpenTelemetry.Tracer.set_attribute(key, value)
150+
OpenTelemetry.Tracer.set_attributes(attributes)
151+
```
152+
153+
Be aware that there are [configurable limits](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/common/common.md#attribute-limits) on the number and size of
154+
Attributes per Span.
155+
156+
#### Adding Events
157+
158+
[Adding
159+
Events](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#add-events)
160+
can be done by passing the name of the event and the
161+
[Attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/common/common.md#attributes)
162+
to associate with it or as a list of Events. Each Event in the list of Events is
163+
a map containing the timestamp, name, and Attributes which can be created with
164+
the function `event/2` and `event/3` in the `opentelemetry` and `OpenTelemetry`
165+
modules.
166+
167+
``` erlang
168+
?add_event(Name, Attributes)
169+
?add_events(Events)
170+
```
171+
172+
``` elixir
173+
OpenTelemetry.Tracer.add_event(event, attributes)
174+
OpenTelemetry.Tracer.add_events(events)
175+
```
176+
177+
#### Setting the Status
178+
179+
[Set
180+
Status](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#set-status)
181+
will override the default Span Status of `Unset`. A Status is a code (`ok`,
182+
`error` or `unset`) and, only if the code is `error`, an optional message string
183+
that describes the error.
184+
185+
``` erlang
186+
?set_status(Code, Message)
187+
```
188+
189+
``` elixir
190+
OpenTelemetry.Tracer.set_status(code, message)
191+
```
192+
193+
#### Update Span Name
194+
195+
[Updating the Span
196+
name](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md#updatename)
197+
can be done after starting the Span but must be done before the Span is end'ed.
198+
199+
``` erlang
200+
?update_name(Name)
201+
```
202+
203+
``` elixir
204+
OpenTelemetry.Tracer.update_name(name)
205+
```
206+
44207
### Including the OpenTelemetry SDK
45208

46209
When only the API is available at runtime a no-op Tracer is used and no Traces
@@ -54,7 +217,7 @@ not as a dependency of any individual Application.
54217

55218
Included in the same [Github
56219
repo](https://github.com/open-telemetry/opentelemetry-erlang) as the API and SDK are an exporter for the [OpenTelemetry Protocol
57-
(OTLP)](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md)
220+
(OTLP)](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/protocol/otlp.md)
58221
and [Zipkin](https://zipkin.io/):
59222

60223
- [OpenTelemetry Protocol](https://hex.pm/packages/opentelemetry_exporter)

apps/opentelemetry_api/include/otel_tracer.hrl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@
4242
-define(add_events(Events),
4343
otel_span:add_events(?current_span_ctx, Events)).
4444

45-
-define(set_status(Status),
46-
otel_span:set_status(?current_span_ctx, Status)).
45+
-define(set_status(Code, Message),
46+
otel_span:set_status(?current_span_ctx, Code, Message)).
47+
48+
-define(set_status(StatusOrCode),
49+
otel_span:set_status(?current_span_ctx, StatusOrCode)).
4750

4851
-define(update_name(Name),
4952
otel_span:update_name(?current_span_ctx, Name)).

apps/opentelemetry_api/lib/open_telemetry.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ defmodule OpenTelemetry do
105105
"""
106106
@type status() :: :opentelemetry.status()
107107

108+
@type status_code() :: :opentelemetry.status_code()
109+
108110
defdelegate get_tracer(name), to: :opentelemetry
109111
defdelegate get_tracer(name, vsn, schema_url), to: :opentelemetry
110112
defdelegate set_default_tracer(t), to: :opentelemetry
@@ -188,6 +190,12 @@ defmodule OpenTelemetry do
188190
@spec events(list()) :: [event()]
189191
defdelegate events(event_list), to: :opentelemetry
190192

193+
@doc """
194+
Creates a Status with an empty description.
195+
"""
196+
@spec status(:opentelemetry.status_code()) :: status()
197+
defdelegate status(code), to: :opentelemetry
198+
191199
@doc """
192200
Creates a Status.
193201
"""

apps/opentelemetry_api/lib/open_telemetry/tracer.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,16 @@ defmodule OpenTelemetry.Tracer do
167167
:otel_span.add_events(:otel_tracer.current_span_ctx(), events)
168168
end
169169

170+
@doc """
171+
Creates and sets the Status of the currently active Span.
172+
173+
If used, this will override the default Span Status, which is `:unset`.
174+
"""
175+
@spec set_status(OpenTelemetry.status_code(), String.t()) :: boolean()
176+
def set_status(code, message) do
177+
:otel_span.set_status(:otel_tracer.current_span_ctx(), code, message)
178+
end
179+
170180
@doc """
171181
Sets the Status of the currently active Span.
172182

0 commit comments

Comments
 (0)