Skip to content

Commit b8185bd

Browse files
authored
Merge pull request #331 from tsloughter/schema-url
Support for Schema URLs on the Resource and Tracer (Instrumentation Library)
2 parents 84e7cf9 + b4a85e1 commit b8185bd

22 files changed

+403
-182
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525
- `OTEL_SPAN_LINK_COUNT_LIMIT`: Limit number of Links on a Span.
2626
- `OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT`: Limit on number of Attributes on an Event.
2727
- `OTEL_LINK_ATTRIBUTE_COUNT_LIMIT`: Limit on number of Attributes on a Link.
28-
28+
- Support for a Schema URL in a Resource and in the Instrumentation Library
29+
information of a Tracer. For more information about Schema URLs see the
30+
specification for [Resources](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/resource/sdk.md)
31+
and [getting a Tracer](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/trace/api.md).
32+
- `OTEL_CREATE_APPLICATION_TRACERS` is a new environment variable,
33+
`create_application_tracers` is the application environment key, for disabling
34+
the automatic creation of Tracers for every Application at boot. The old keys,
35+
`OTEL_REGISTER_LOADED_APPLICATIONS` and `register_loaded_applications`, will
36+
continue to work as well.
37+
2938
### Removed
3039

3140
- The `sampler` option to `start_span` and `with_span` was removed.
41+
- `register_tracer` has been removed and now `get_tracer` will cache the Tracer
42+
after creation if needed.
3243

3344
## [API 1.0.0-rc.3.2] - 2021-10-13
3445

apps/opentelemetry/include/otel_resource.hrl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
%% The schema url for telemetry resources
2+
-define(SCHEMA_URL, <<"https://opentelemetry.io/schemas/1.8.0">>).
3+
14
%% if any `service' attribute is defined then
25
%% `service.name' and `service.instance.id' are required
36

apps/opentelemetry/src/opentelemetry_app.erl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ start(_StartType, _StartArgs) ->
3838
%% must be done after the supervisor starts so that otel_tracer_server is running
3939
%% TODO: make this work with release upgrades. Currently if an application's version
4040
%% changes the version in the tracer will not be updated.
41-
register_loaded_application_tracers(Config),
41+
create_loaded_application_tracers(Config),
4242

4343
SupResult.
4444

@@ -59,10 +59,10 @@ setup_text_map_propagators(#{text_map_propagators := List}) ->
5959
CompositePropagator = otel_propagator_text_map_composite:create(List),
6060
opentelemetry:set_text_map_propagator(CompositePropagator).
6161

62-
register_loaded_application_tracers(#{register_loaded_applications := true}) ->
62+
create_loaded_application_tracers(#{create_application_tracers := true}) ->
6363
%% TODO: filter out OTP apps that will not have any instrumentation
6464
LoadedApplications = application:loaded_applications(),
65-
opentelemetry:register_applications(LoadedApplications),
65+
opentelemetry:create_application_tracers(LoadedApplications),
6666
ok;
67-
register_loaded_application_tracers(_) ->
67+
create_loaded_application_tracers(_) ->
6868
ok.

apps/opentelemetry/src/otel_configuration.erl

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
%% required configuration
2929
%% using a map instead of a record because there can be more values
3030
-type t() :: #{log_level := atom(),
31-
register_loaded_applications := boolean(),
31+
register_loaded_applications := boolean() | undefined,
32+
create_application_tracers := boolean() | undefined,
3233
id_generator := module(),
3334
deny_list := [atom()],
3435
resource_detectors => [module()],
@@ -51,7 +52,8 @@
5152
-spec new() -> t().
5253
new() ->
5354
#{log_level => info,
54-
register_loaded_applications => true,
55+
register_loaded_applications => undefined,
56+
create_application_tracers => undefined,
5557
id_generator => otel_id_generator,
5658
deny_list => [],
5759
resource_detectors => [otel_resource_env_var,
@@ -93,7 +95,26 @@ span_limits(AppEnv, ConfigMap) ->
9395

9496
-spec general(list(), t()) -> t().
9597
general(AppEnv, ConfigMap) ->
96-
merge_list_with_environment(config_mappings(general_sdk), AppEnv, ConfigMap).
98+
Config = merge_list_with_environment(config_mappings(general_sdk), AppEnv, ConfigMap),
99+
100+
%% merge the old `register_loaded_applications' with the new config key
101+
%% `create_application_tracers' that has replaced it
102+
Config1 = maps:update_with(create_application_tracers,
103+
fun(undefined) ->
104+
%% `create_application_tracers' isn't set so update
105+
%% with the `register_loaded_applications' value
106+
%% or `true' if it too isn't set
107+
case maps:get(register_loaded_applications, Config) of
108+
undefined ->
109+
true;
110+
Bool ->
111+
Bool
112+
end;
113+
(Bool) ->
114+
Bool
115+
end, Config),
116+
117+
Config1.
97118

98119
-spec sweeper(list(), t()) -> t().
99120
sweeper(AppEnv, ConfigMap=#{sweeper := DefaultSweeperConfig}) ->
@@ -231,13 +252,17 @@ report_cb(#{source := transform,
231252

232253
config_mappings(general_sdk) ->
233254
[{"OTEL_LOG_LEVEL", log_level, existing_atom},
255+
256+
%% `register_loaded_applications' is kept for backwards compatibility
234257
{"OTEL_REGISTER_LOADED_APPLICATIONS", register_loaded_applications, boolean},
258+
{"OTEL_CREATE_APPLICATION_TRACERS", create_application_tracers, boolean},
259+
235260
{"OTEL_ID_GENERATOR", id_generator, existing_atom},
236-
{"OTEL_DENY_LIST", deny_list, list},
261+
{"OTEL_DENY_LIST", deny_list, existing_atom_list},
237262
{"OTEL_PROPAGATORS", text_map_propagators, propagators},
238263
{"OTEL_TRACES_EXPORTER", traces_exporter, exporter},
239264
{"OTEL_METRICS_EXPORTER", metrics_exporter, exporter},
240-
{"OTEL_RESOURCE_DETECTORS", resource_detectors, kvlist_value},
265+
{"OTEL_RESOURCE_DETECTORS", resource_detectors, existing_atom_list},
241266
{"OTEL_RESOURCE_DETECTOR_TIMEOUT", resource_detector_timeout, integer}];
242267
config_mappings(otel_batch_processor) ->
243268
[{"OTEL_BSP_SCHEDULE_DELAY_MILLIS", scheduled_delay_ms, integer},
@@ -269,6 +294,19 @@ config_mappings(_) ->
269294

270295
transform(_, undefined) ->
271296
undefined;
297+
transform(existing_atom_list, [A | _]=List) when is_atom(A) ->
298+
List;
299+
transform(existing_atom_list, String) when is_list(String) ->
300+
List = string:split(String, ",", all),
301+
lists:filtermap(fun(A) ->
302+
try transform(existing_atom, string:trim(A)) of
303+
Value ->
304+
{true, Value}
305+
catch
306+
_:_ ->
307+
false
308+
end
309+
end, List);
272310
transform(exporter, "otlp") ->
273311
{opentelemetry_exporter, #{}};
274312
transform(exporter, "jaeger") ->
@@ -333,7 +371,7 @@ transform(sampler, {"parentbased_traceidratio", Probability}) ->
333371
transform(sampler, Value) ->
334372
Value;
335373
transform(key_value_list, Value) when is_list(Value) ->
336-
case io_lib:printable_list(Value) of
374+
case io_lib:printable_unicode_list(Value) of
337375
true ->
338376
Pairs = string:split(Value, ",", all),
339377
lists:filtermap(fun(Pair) ->

apps/opentelemetry/src/otel_resource.erl

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,33 @@
1919
-module(otel_resource).
2020

2121
-export([create/1,
22+
create/2,
2223
merge/2,
24+
schema_url/1,
2325
attributes/1]).
2426

25-
-type key() :: unicode:latin1_chardata().
26-
-type value() :: unicode:latin1_chardata() | integer() | float() | boolean().
27-
-type resource() :: {otel_resource, otel_attributes:t()}.
28-
-opaque t() :: resource().
27+
-include("otel_resource.hrl").
2928

30-
-export_type([t/0]).
29+
-type key() :: unicode:latin1_binary().
30+
%% values allowed in attributes of a resource are limited
31+
-type value() :: unicode:latin1_binary() | integer() | float() | boolean().
32+
-type schema_url() :: uri_string:uri_string().
3133

3234
-define(MAX_LENGTH, 255).
3335

34-
%% verifies each key and value and drops any that don't pass verification
36+
-record(resource, {schema_url :: schema_url() | undefined,
37+
attributes :: otel_attributes:t()}).
38+
-type t() :: #resource{}.
39+
3540
-spec create(#{key() => value()} | [{key(), value()}]) -> t().
36-
create(Map) when is_map(Map) ->
37-
create(maps:to_list(Map));
38-
create(List) when is_list(List) ->
41+
create(Attributes) ->
42+
create(Attributes, undefined).
43+
44+
%% verifies each key and value and drops any that don't pass verification
45+
-spec create(#{key() => value()} | [{key(), value()}], schema_url() | undefined) -> t().
46+
create(Map, SchemaUrl) when is_map(Map) ->
47+
create(maps:to_list(Map), SchemaUrl);
48+
create(List, SchemaUrl) when is_list(List) ->
3949
List1 = lists:filtermap(fun({K, V}) ->
4050
%% TODO: log an info or debug message when dropping?
4151
case check_key(K) andalso check_value(V) of
@@ -45,20 +55,43 @@ create(List) when is_list(List) ->
4555
false
4656
end
4757
end, lists:ukeysort(1, List)),
48-
{otel_resource, otel_attributes:new(List1, 128, 255)}.
58+
#resource{schema_url=SchemaUrl,
59+
attributes=otel_attributes:new(List1, 128, 255)}.
60+
61+
-spec schema_url(t()) -> schema_url() | undefined.
62+
schema_url(#resource{schema_url=Schema}) ->
63+
Schema.
4964

5065
-spec attributes(t()) -> otel_attributes:t().
51-
attributes({otel_resource, Attributes}) ->
66+
attributes(#resource{attributes=Attributes}) ->
5267
Attributes.
5368

54-
%% In case of collision the updating, first argument, resource takes precedence.
69+
%% in case of collision the updating, first argument, resource takes precedence.
5570
-spec merge(t(), t()) -> t().
56-
merge({otel_resource, NewAttributes}, {otel_resource, CurrentAttributes}) ->
71+
merge(#resource{schema_url=NewSchemaUrl,
72+
attributes=NewAttributes}, Current=#resource{schema_url=CurrentSchemaUrl,
73+
attributes=CurrentAttributes}) ->
74+
SchameUrl = merge_schema_url(NewSchemaUrl, CurrentSchemaUrl),
5775
NewMap = otel_attributes:map(NewAttributes),
58-
{otel_resource, otel_attributes:set(NewMap, CurrentAttributes)}.
76+
Current#resource{schema_url=SchameUrl,
77+
attributes=otel_attributes:set(NewMap, CurrentAttributes)}.
5978

6079
%%
6180

81+
%% when merging resources the schemas are checked to verify they match.
82+
%% if they do match then the schema is set to `undefined' and the attributes are kept.
83+
merge_schema_url(SchemaUrl, undefined) ->
84+
SchemaUrl;
85+
merge_schema_url(undefined, SchemaUrl) ->
86+
SchemaUrl;
87+
merge_schema_url(NewSchemaUrl, CurrentSchemaUrl) ->
88+
merge_schema_url_(uri_string:normalize(NewSchemaUrl), uri_string:normalize(CurrentSchemaUrl)).
89+
90+
merge_schema_url_(SchemaUrl, SchemaUrl) ->
91+
SchemaUrl;
92+
merge_schema_url_(_, _) ->
93+
undefined.
94+
6295
%% all resource strings, key or value, must be latin1 with length less than 255
6396
check_string(S) ->
6497
string:length(S) =< ?MAX_LENGTH.

apps/opentelemetry/src/otel_resource_app_env.erl

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,27 @@ parse(_) ->
5050
parse_values(Key, Values) when is_map(Values) ->
5151
parse_values(Key, maps:to_list(Values));
5252
parse_values(Key, Values) when is_list(Values) ->
53-
lists:flatmap(fun({SubKey, Value=[{_,_}|_]}) ->
54-
%% list of tuples means we have more subkeys
55-
parse_values([Key, ".", to_string(SubKey)], Value);
56-
({SubKey, Value}) when is_map(Value) ->
57-
%% map value means we have more subkeys
58-
parse_values([Key, ".", to_string(SubKey)], Value);
59-
({SubKey, Value})->
60-
[{unicode:characters_to_list([Key, ".", to_string(SubKey)]), Value}]
61-
end, Values);
53+
case io_lib:printable_unicode_list(Values) of
54+
true ->
55+
[{unicode:characters_to_binary(Key), unicode:characters_to_binary(Values)}];
56+
false ->
57+
lists:flatmap(fun({SubKey, Value=[{_,_}|_]}) ->
58+
%% list of tuples means we have more subkeys
59+
parse_values([Key, ".", to_string(SubKey)], Value);
60+
({SubKey, Value}) when is_map(Value) ->
61+
%% map value means we have more subkeys
62+
parse_values([Key, ".", to_string(SubKey)], Value);
63+
({SubKey, Value})->
64+
[{unicode:characters_to_binary([Key, ".", to_string(SubKey)]), Value}]
65+
end, Values)
66+
end;
6267
parse_values(Key, Value) ->
63-
[{unicode:characters_to_list(Key), Value}].
68+
[{unicode:characters_to_binary(Key), Value}].
6469

65-
-spec to_string(atom() | binary() | list()) -> list().
70+
-spec to_string(atom() | binary() | list()) -> binary().
6671
to_string(K) when is_atom(K) ->
67-
atom_to_list(K);
68-
to_string(K) when is_binary(K) ->
69-
binary_to_list(K);
72+
atom_to_binary(K, utf8);
73+
to_string(K) when is_list(K) ->
74+
list_to_binary(K);
7075
to_string(K) ->
7176
K.

apps/opentelemetry/src/otel_resource_detector.erl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ required_attributes(Resource) ->
166166
process_attributes() ->
167167
OtpVsn = otp_vsn(),
168168
ErtsVsn = erts_vsn(),
169-
[{?PROCESS_RUNTIME_NAME, emulator()},
170-
{?PROCESS_RUNTIME_VERSION, ErtsVsn},
171-
{?PROCESS_RUNTIME_DESCRIPTION, runtime_description(OtpVsn, ErtsVsn)}].
169+
[{?PROCESS_RUNTIME_NAME, unicode:characters_to_binary(emulator())},
170+
{?PROCESS_RUNTIME_VERSION, unicode:characters_to_binary(ErtsVsn)},
171+
{?PROCESS_RUNTIME_DESCRIPTION, unicode:characters_to_binary(runtime_description(OtpVsn, ErtsVsn))}].
172172

173173
runtime_description(OtpVsn, ErtsVsn) ->
174174
io_lib:format("Erlang/OTP ~s erts-~s", [OtpVsn, ErtsVsn]).
@@ -185,7 +185,7 @@ emulator() ->
185185
prog_name() ->
186186
%% RELEASE_PROG is set by mix and rebar3 release scripts
187187
%% PROGNAME is an OS variable set by `erl' and rebar3 release scripts
188-
os_or_default("RELEASE_PROG", os_or_default("PROGNAME", "erl")).
188+
unicode:characters_to_binary(os_or_default("RELEASE_PROG", os_or_default("PROGNAME", <<"erl">>))).
189189

190190
os_or_default(EnvVar, Default) ->
191191
case os:getenv(EnvVar) of
@@ -232,7 +232,9 @@ add_service_name(Resource, ProgName) ->
232232
ServiceName ->
233233
%% service.name resource first to override any other service.name
234234
%% attribute that could be set in the resource
235-
otel_resource:merge(otel_resource:create([{?SERVICE_NAME, ServiceName}]), Resource)
235+
ServiceNameResource = otel_resource:create([{?SERVICE_NAME,
236+
unicode:characters_to_binary(ServiceName)}]),
237+
otel_resource:merge(ServiceNameResource, Resource)
236238
end.
237239

238240
service_release_name(ProgName) ->
@@ -244,5 +246,5 @@ service_release_name(ProgName) ->
244246
_ -> [{?SERVICE_VERSION, RelVsn}]
245247
end]);
246248
_ ->
247-
otel_resource:create([{?SERVICE_NAME, "unknown_service:" ++ ProgName}])
249+
otel_resource:create([{?SERVICE_NAME, <<"unknown_service:", ProgName/binary>>}])
248250
end.

apps/opentelemetry/src/otel_tracer_default.erl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
-behaviour(otel_tracer).
2121

2222
-export([start_span/4,
23-
with_span/5
24-
]).
23+
with_span/5]).
2524

2625
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
2726
-include("otel_tracer.hrl").
@@ -53,4 +52,3 @@ with_span(Ctx, Tracer, SpanName, Opts, Fun) ->
5352
_ = otel_span_ets:end_span(SpanCtx),
5453
otel_ctx:detach(Ctx)
5554
end.
56-

apps/opentelemetry/src/otel_tracer_server.erl

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,22 @@ init(#{id_generator := IdGeneratorModule,
9393

9494
handle_call(resource, _From, State=#state{resource=Resource}) ->
9595
{reply, Resource, State};
96-
handle_call({get_tracer, InstrumentationLibrary}, _From, State=#state{shared_tracer=Tracer,
97-
deny_list=_DenyList}) ->
98-
{reply, {Tracer#tracer.module,
99-
Tracer#tracer{instrumentation_library=InstrumentationLibrary}}, State};
100-
handle_call({register_tracer, Name, Vsn}, _From, State=#state{shared_tracer=Tracer,
101-
deny_list=DenyList}) ->
96+
handle_call({get_tracer, Name, Vsn, SchemaUrl}, _From, State=#state{shared_tracer=Tracer,
97+
deny_list=DenyList}) ->
10298
%% TODO: support semver constraints in denylist
10399
case proplists:is_defined(Name, DenyList) of
104100
true ->
105-
_ = opentelemetry:set_tracer(Name, {otel_tracer_noop, []}),
106-
{reply, ok, State};
101+
{reply, {otel_tracer_noop, []}, State};
107102
false ->
108-
InstrumentationLibrary = opentelemetry:instrumentation_library(Name, Vsn),
103+
InstrumentationLibrary = opentelemetry:instrumentation_library(Name, Vsn, SchemaUrl),
109104
TracerTuple = {Tracer#tracer.module,
110105
Tracer#tracer{instrumentation_library=InstrumentationLibrary}},
111-
_ = opentelemetry:set_tracer(Name, TracerTuple),
112-
{reply, ok, State}
106+
{reply, TracerTuple, State}
113107
end;
108+
handle_call({get_tracer, InstrumentationLibrary}, _From, State=#state{shared_tracer=Tracer,
109+
deny_list=_DenyList}) ->
110+
{reply, {Tracer#tracer.module,
111+
Tracer#tracer{instrumentation_library=InstrumentationLibrary}}, State};
114112
handle_call(force_flush, _From, State=#state{processors=Processors}) ->
115113
Reply = lists:foldl(fun(Processor, Result) ->
116114
case force_flush_(Processor) of

0 commit comments

Comments
 (0)