diff --git a/apps/opentelemetry/include/otel_sampler.hrl b/apps/opentelemetry/include/otel_sampler.hrl index 1adc9b43..cdfcd418 100644 --- a/apps/opentelemetry/include/otel_sampler.hrl +++ b/apps/opentelemetry/include/otel_sampler.hrl @@ -16,6 +16,6 @@ %% @end %%%------------------------------------------------------------------------- --define(NOT_RECORD, not_record). --define(RECORD, record). --define(RECORD_AND_SAMPLED, record_and_sampled). +-define(DROP, drop). +-define(RECORD_ONLY, record_only). +-define(RECORD_AND_SAMPLE, record_and_sample). diff --git a/apps/opentelemetry/include/otel_span.hrl b/apps/opentelemetry/include/otel_span.hrl index c60bb722..ec2cd5f2 100644 --- a/apps/opentelemetry/include/otel_span.hrl +++ b/apps/opentelemetry/include/otel_span.hrl @@ -63,7 +63,7 @@ status :: opentelemetry:status() | undefined, %% 8-bit integer, lowest bit is if it is sampled - trace_options = 1 :: integer() | undefined, + trace_flags = 1 :: integer() | undefined, %% this field is not propagated and is only here as an implementation optimization %% If true updates like adding events are done on the span. The same as if the diff --git a/apps/opentelemetry/src/otel_batch_processor.erl b/apps/opentelemetry/src/otel_batch_processor.erl index ded6da89..2f12966e 100644 --- a/apps/opentelemetry/src/otel_batch_processor.erl +++ b/apps/opentelemetry/src/otel_batch_processor.erl @@ -88,6 +88,8 @@ on_start(_Ctx, Span, _) -> -spec on_end(opentelemetry:span(), otel_span_processor:processor_config()) -> true | dropped | {error, invalid_span} | {error, no_export_buffer}. +on_end(#span{trace_flags=TraceFlags}, _) when not(?IS_SAMPLED(TraceFlags)) -> + dropped; on_end(Span=#span{}, _) -> do_insert(Span); on_end(_Span, _) -> diff --git a/apps/opentelemetry/src/otel_propagator_http_b3.erl b/apps/opentelemetry/src/otel_propagator_http_b3.erl index bfe0c26b..1d8aee29 100644 --- a/apps/opentelemetry/src/otel_propagator_http_b3.erl +++ b/apps/opentelemetry/src/otel_propagator_http_b3.erl @@ -29,7 +29,7 @@ -define(B3_SPAN_ID, <<"X-B3-SpanId">>). -define(B3_SAMPLED, <<"X-B3-Sampled">>). --define(IS_SAMPLED(S), S =:= "1" orelse S =:= <<"1">> orelse S =:= "true" orelse S =:= <<"true">>). +-define(B3_IS_SAMPLED(S), S =:= "1" orelse S =:= <<"1">> orelse S =:= "true" orelse S =:= <<"true">>). -spec inject(opentelemetry:span_ctx() | undefined) -> otel_propagator:text_map(). inject(#span_ctx{trace_id=TraceId, @@ -54,9 +54,9 @@ extract(Headers, _) when is_list(Headers) -> TraceId = trace_id(Headers), SpanId = span_id(Headers), Sampled = lookup(?B3_SAMPLED, Headers), - #span_ctx{trace_id=string_to_integer(TraceId, 16), - span_id=string_to_integer(SpanId, 16), - trace_flags=case Sampled of True when ?IS_SAMPLED(True) -> 1; _ -> 0 end} + otel_tracer:non_recording_span(string_to_integer(TraceId, 16), + string_to_integer(SpanId, 16), + case Sampled of True when ?B3_IS_SAMPLED(True) -> 1; _ -> 0 end) catch throw:invalid -> undefined; diff --git a/apps/opentelemetry/src/otel_propagator_http_w3c.erl b/apps/opentelemetry/src/otel_propagator_http_w3c.erl index 409e3005..ebe822b9 100644 --- a/apps/opentelemetry/src/otel_propagator_http_w3c.erl +++ b/apps/opentelemetry/src/otel_propagator_http_w3c.erl @@ -136,9 +136,9 @@ to_span_ctx(Version, TraceId, SpanId, Opts) -> try %% verify version is hexadecimal _ = binary_to_integer(Version, 16), - #span_ctx{trace_id=binary_to_integer(TraceId, 16), - span_id=binary_to_integer(SpanId, 16), - trace_flags=case Opts of <<"01">> -> 1; <<"00">> -> 0; _ -> error(badarg) end} + otel_tracer:non_recording_span(binary_to_integer(TraceId, 16), + binary_to_integer(SpanId, 16), + case Opts of <<"01">> -> 1; <<"00">> -> 0; _ -> error(badarg) end) catch %% to integer from base 16 string failed error:badarg -> diff --git a/apps/opentelemetry/src/otel_sampler.erl b/apps/opentelemetry/src/otel_sampler.erl index e08bcc4e..de9b1802 100644 --- a/apps/opentelemetry/src/otel_sampler.erl +++ b/apps/opentelemetry/src/otel_sampler.erl @@ -33,11 +33,11 @@ -callback setup(module(), map()) -> t(). --type sampling_decision() :: ?NOT_RECORD | ?RECORD | ?RECORD_AND_SAMPLED. +-type sampling_decision() :: ?DROP | ?RECORD_ONLY | ?RECORD_AND_SAMPLE. -type sampling_result() :: {sampling_decision(), opentelemetry:attributes(), opentelemetry:tracestate()}. -type description() :: unicode:unicode_binary(). --type sampler() :: {fun((opentelemetry:trace_id(), - opentelemetry:span_ctx() | undefined, +-type sampler() :: {fun((otel_ctx:t(), + opentelemetry:trace_id(), opentelemetry:links(), opentelemetry:span_name(), opentelemetry:kind(), @@ -71,11 +71,11 @@ setup(trace_id_ratio_based, Probability) -> setup(Sampler, Opts) -> Sampler:setup(Opts). -always_on(_TraceId, SpanCtx, _Links, _SpanName, _Kind, _Attributes, _Opts) -> - {?RECORD_AND_SAMPLED, [], tracestate(SpanCtx)}. +always_on(Ctx, _TraceId, _Links, _SpanName, _Kind, _Attributes, _Opts) -> + {?RECORD_AND_SAMPLE, [], tracestate(Ctx)}. -always_off(_TraceId, SpanCtx, _Links, _SpanName, _Kind, _Attributes, _Opts) -> - {?NOT_RECORD, [], tracestate(SpanCtx)}. +always_off(Ctx, _TraceId, _Links, _SpanName, _Kind, _Attributes, _Opts) -> + {?DROP, [], tracestate(Ctx)}. -spec get_description(sampler()) -> description(). get_description({_Fun, Description, _Opts}) -> @@ -106,14 +106,15 @@ parent_based_config(Opts) -> ?LOG_INFO("no root opt found for sampler parent_based. always_on will be used for root spans"), parent_based_config(Opts#{root => {always_on, #{}}}). -parent_based(TraceId, SpanCtx, Links, SpanName, Kind, Attributes, Opts) -> - {Sampler, _Description, SamplerOpts} = parent_based_sampler(SpanCtx, Opts), - Sampler(TraceId, SpanCtx, Links, SpanName, Kind, Attributes, SamplerOpts). +parent_based(Ctx, TraceId, Links, SpanName, Kind, Attributes, Opts) -> + ParentSpanCtx = otel_tracer:current_span_ctx(Ctx), + {Sampler, _Description, SamplerOpts} = parent_based_sampler(ParentSpanCtx, Opts), + Sampler(Ctx, TraceId, Links, SpanName, Kind, Attributes, SamplerOpts). %% remote parent sampled parent_based_sampler(#span_ctx{trace_flags=TraceFlags, is_remote=true}, #{remote_parent_sampled := SamplerAndOpts}) - when ?IS_SPAN_ENABLED(TraceFlags) -> + when ?IS_SAMPLED(TraceFlags) -> SamplerAndOpts; %% remote parent not sampled parent_based_sampler(#span_ctx{is_remote=true}, #{remote_parent_not_sampled := SamplerAndOpts}) -> @@ -121,7 +122,7 @@ parent_based_sampler(#span_ctx{is_remote=true}, #{remote_parent_not_sampled := S %% local parent sampled parent_based_sampler(#span_ctx{trace_flags=TraceFlags, is_remote=false}, #{local_parent_sampled := SamplerAndOpts}) - when ?IS_SPAN_ENABLED(TraceFlags) -> + when ?IS_SAMPLED(TraceFlags) -> SamplerAndOpts; %% local parent not sampled parent_based_sampler(#span_ctx{is_remote=false}, #{local_parent_not_sampled := SamplerAndOpts}) -> @@ -130,20 +131,28 @@ parent_based_sampler(#span_ctx{is_remote=false}, #{local_parent_not_sampled := S parent_based_sampler(_SpanCtx, #{root := SamplerAndOpts}) -> SamplerAndOpts. -trace_id_ratio_based(TraceId, SpanCtx, _, _, _, _, IdUpperBound) -> + +trace_id_ratio_based(Ctx, undefined, _, _, _, _, _IdUpperBound) -> + {?DROP, [], tracestate(Ctx)}; +trace_id_ratio_based(Ctx, 0, _, _, _, _, _IdUpperBound) -> + {?DROP, [], tracestate(Ctx)}; +trace_id_ratio_based(Ctx, TraceId, _, _, _, _, IdUpperBound) -> Lower64Bits = TraceId band ?MAX_VALUE, case erlang:abs(Lower64Bits) < IdUpperBound of true -> - {?RECORD_AND_SAMPLED, [], tracestate(SpanCtx)}; + {?RECORD_AND_SAMPLE, [], tracestate(Ctx)}; false -> - {?NOT_RECORD, [], tracestate(SpanCtx)} + {?DROP, [], tracestate(Ctx)} end. -tracestate(#span_ctx{tracestate=undefined}) -> +tracestate(Ctx) -> + tracestate_(otel_tracer:current_span_ctx(Ctx)). + +tracestate_(#span_ctx{tracestate=undefined}) -> []; -tracestate(#span_ctx{tracestate=TraceState}) -> +tracestate_(#span_ctx{tracestate=TraceState}) -> TraceState; -tracestate(undefined) -> +tracestate_(undefined) -> []. description(always_on, _) -> diff --git a/apps/opentelemetry/src/otel_span_ets.erl b/apps/opentelemetry/src/otel_span_ets.erl index 977f3eab..c9ca436d 100644 --- a/apps/opentelemetry/src/otel_span_ets.erl +++ b/apps/opentelemetry/src/otel_span_ets.erl @@ -25,7 +25,7 @@ handle_call/3, handle_cast/2]). --export([start_span/6, +-export([start_span/5, end_span/1, end_span/2, get_ctx/1, @@ -47,15 +47,21 @@ start_link(Opts) -> gen_server:start_link(?MODULE, Opts, []). %% @doc Start a span and insert into the active span ets table. --spec start_span(otel_ctx:t(), opentelemetry:span_name(), opentelemetry:span_ctx() | undefined, - otel_span:start_opts(), fun(), otel_tracer_server:instrumentation_library()) - -> opentelemetry:span_ctx(). -start_span(Ctx, Name, ParentSpanCtx, Opts, Processors, InstrumentationLibrary) -> - {SpanCtx, Span} = otel_span_utils:start_span(Name, ParentSpanCtx, Opts), - Span1 = Span#span{instrumentation_library=InstrumentationLibrary}, - Span2 = Processors(Ctx, Span1), - _ = storage_insert(Span2), - SpanCtx. +-spec start_span(otel_ctx:t(), opentelemetry:span_name(), otel_span:start_opts(), + fun(), otel_tracer_server:instrumentation_library()) -> opentelemetry:span_ctx(). +start_span(Ctx, Name, Opts, Processors, InstrumentationLibrary) -> + case otel_span_utils:start_span(Ctx, Name, Opts) of + {SpanCtx=#span_ctx{is_recording=true}, Span=#span{}} -> + Span1 = Span#span{instrumentation_library=InstrumentationLibrary}, + Span2 = Processors(Ctx, Span1), + _ = storage_insert(Span2), + SpanCtx; + {SpanCtx, Span=#span{}} -> + %% span isn't recorded so don't run processors + %% but we do insert to ets table? + _ = storage_insert(Span), + SpanCtx + end. end_span(SpanCtx=#span_ctx{span_sdk={_, OnEndProcessors}}) -> end_span(SpanCtx, OnEndProcessors). @@ -63,8 +69,8 @@ end_span(SpanCtx=#span_ctx{span_sdk={_, OnEndProcessors}}) -> %% @doc End a span based on its context and send to reporter. -spec end_span(opentelemetry:span_ctx(), fun()) -> boolean() | {error, term()}. end_span(#span_ctx{span_id=SpanId, - tracestate=Tracestate, - trace_flags=TraceOptions}, Processors) when ?IS_SPAN_ENABLED(TraceOptions) -> + is_recording=true, + tracestate=Tracestate}, Processors) -> case ets:take(?SPAN_TAB, SpanId) of [Span] -> Span1 = otel_span_utils:end_span(Span#span{tracestate=Tracestate}), @@ -136,6 +142,8 @@ update_name(#span_ctx{span_id=SpanId}, Name) -> %% +storage_insert(undefined) -> + ok; storage_insert(Span) -> ets:insert(?SPAN_TAB, Span). diff --git a/apps/opentelemetry/src/otel_span_utils.erl b/apps/opentelemetry/src/otel_span_utils.erl index 17be9892..5ed9deed 100644 --- a/apps/opentelemetry/src/otel_span_utils.erl +++ b/apps/opentelemetry/src/otel_span_utils.erl @@ -25,38 +25,24 @@ -include("otel_sampler.hrl"). -include("otel_span.hrl"). -%% sampling bit is the first bit in 8-bit trace options --define(IS_ENABLED(X), (X band 1) =/= 0). - --spec start_span(opentelemetry:span_name(), opentelemetry:span_ctx() | undefined, otel_span:start_opts()) +-spec start_span(otel_ctx:t(), opentelemetry:span_name(), otel_span:start_opts()) -> {opentelemetry:span_ctx(), opentelemetry:span() | undefined}. -start_span(Name, Parent, Opts) -> +start_span(Ctx, Name, Opts) -> Attributes = maps:get(attributes, Opts, []), Links = maps:get(links, Opts, []), Kind = maps:get(kind, Opts, ?SPAN_KIND_INTERNAL), Sampler = maps:get(sampler, Opts), StartTime = maps:get(start_time, Opts, opentelemetry:timestamp()), - new_span(Name, Parent, Sampler, StartTime, Kind, Attributes, Links). - -%% if parent is undefined create a new trace id -new_span(Name, undefined, Sampler, StartTime, Kind, Attributes, Links) -> - TraceId = opentelemetry:generate_trace_id(), - Span = #span_ctx{trace_id=TraceId, - trace_flags=0}, - new_span(Name, Span, Sampler, StartTime, Kind, Attributes, Links); -new_span(Name, Parent=#span_ctx{trace_id=TraceId, - span_id=ParentSpanId}, Sampler, StartTime, Kind, Attributes, Links) -> - SpanId = opentelemetry:generate_span_id(), - SpanCtx = Parent#span_ctx{span_id=SpanId}, + new_span(Ctx, Name, Sampler, StartTime, Kind, Attributes, Links). +new_span(Ctx, Name, Sampler, StartTime, Kind, Attributes, Links) -> + ParentTraceId = trace_id(Ctx), {TraceFlags, IsRecording, SamplerAttributes, TraceState} = - sample(Sampler, TraceId, case Parent of - #span_ctx{span_id=undefined} -> - undefined; - _ -> - Parent - end, - Links, Name, Kind, Attributes), + sample(Ctx, Sampler, ParentTraceId, Links, Name, Kind, Attributes), + + {NewSpanCtx, ParentSpanId} = new_span_ctx(Ctx), + TraceId = NewSpanCtx#span_ctx.trace_id, + SpanId = NewSpanCtx#span_ctx.span_id, Span = #span{trace_id=TraceId, span_id=SpanId, @@ -67,10 +53,38 @@ new_span(Name, Parent=#span_ctx{trace_id=TraceId, name=Name, attributes=Attributes++SamplerAttributes, links=Links, + trace_flags=TraceFlags, is_recording=IsRecording}, - {SpanCtx#span_ctx{trace_flags=TraceFlags, - is_recording=IsRecording}, Span}. + {NewSpanCtx#span_ctx{trace_flags=TraceFlags, + is_valid=true, + is_recording=IsRecording}, Span}. + +trace_id(Ctx) -> + case otel_tracer:current_span_ctx(Ctx) of + #span_ctx{trace_id=TraceId} -> + TraceId; + _ -> + undefined + end. + +-spec new_span_ctx(otel_ctx:t()) -> {opentelemetry:span_ctx(), opentelemetry:span_id()}. +new_span_ctx(Ctx) -> + case otel_tracer:current_span_ctx(Ctx) of + undefined -> + {root_span_ctx(), undefined}; + #span_ctx{is_valid=false} -> + {root_span_ctx(), undefined}; + ParentSpanCtx=#span_ctx{span_id=ParentSpanId} -> + %% keep the rest of the parent span ctx, simply need to update the span_id + {ParentSpanCtx#span_ctx{span_id=opentelemetry:generate_span_id()}, ParentSpanId} + end. + +root_span_ctx() -> + #span_ctx{trace_id=opentelemetry:generate_trace_id(), + span_id=opentelemetry:generate_span_id(), + is_valid=true, + trace_flags=0}. %%-------------------------------------------------------------------- %% @doc @@ -79,7 +93,7 @@ new_span(Name, Parent=#span_ctx{trace_id=TraceId, %%-------------------------------------------------------------------- -spec end_span(opentelemetry:span()) -> opentelemetry:span(). end_span(Span=#span{end_time=undefined, - trace_options=TraceOptions}) when ?IS_ENABLED(TraceOptions) -> + trace_flags=TraceFlags}) when ?IS_SAMPLED(TraceFlags) -> EndTime = opentelemetry:timestamp(), Span#span{end_time=EndTime}; end_span(Span) -> @@ -87,14 +101,14 @@ end_span(Span) -> %% -sample({Sampler, _Description, Opts}, TraceId, Parent, Links, SpanName, Kind, Attributes) -> - {Decision, NewAttributes, TraceState} = Sampler(TraceId, Parent, Links, - SpanName, Kind, Attributes, Opts), +sample(Ctx, {Sampler, _Description, Opts}, TraceId, Links, SpanName, Kind, Attributes) -> + {Decision, NewAttributes, TraceState} = Sampler(Ctx, TraceId, Links, SpanName, + Kind, Attributes, Opts), case Decision of - ?NOT_RECORD -> + ?DROP -> {0, false, NewAttributes, TraceState}; - ?RECORD -> + ?RECORD_ONLY -> {0, true, NewAttributes, TraceState}; - ?RECORD_AND_SAMPLED -> + ?RECORD_AND_SAMPLE -> {1, true, NewAttributes, TraceState} end. diff --git a/apps/opentelemetry/src/otel_tracer_default.erl b/apps/opentelemetry/src/otel_tracer_default.erl index dfff4a58..b0164554 100644 --- a/apps/opentelemetry/src/otel_tracer_default.erl +++ b/apps/opentelemetry/src/otel_tracer_default.erl @@ -43,9 +43,8 @@ start_span(Tracer={_, #tracer{on_end_processors=OnEndProcessors}}, Name, Opts) - start_span(Ctx, Tracer={_, #tracer{on_start_processors=Processors, on_end_processors=OnEndProcessors, instrumentation_library=InstrumentationLibrary}}, Name, Opts) -> - ParentSpanCtx = maybe_parent_span_ctx(Ctx), Opts1 = maybe_set_sampler(Tracer, Opts), - SpanCtx = otel_span_ets:start_span(Ctx, Name, ParentSpanCtx, Opts1, Processors, InstrumentationLibrary), + SpanCtx = otel_span_ets:start_span(Ctx, Name, Opts1, Processors, InstrumentationLibrary), {SpanCtx#span_ctx{span_sdk={otel_span_ets, OnEndProcessors}}, Ctx}. maybe_set_sampler(_Tracer, Opts) when is_map_key(sampler, Opts) -> @@ -53,20 +52,6 @@ maybe_set_sampler(_Tracer, Opts) when is_map_key(sampler, Opts) -> maybe_set_sampler({_, #tracer{sampler=Sampler}}, Opts) -> Opts#{sampler => Sampler}. -%% returns the span `start_opts' map with the parent span ctx set -%% based on the current tracer ctx, the ctx passed to `start_span' -%% or `start_inactive_span' and in the case none of those are defined it -%% checks the `EXTERNAL_SPAN_CTX' which can be set by a propagator extractor. --spec maybe_parent_span_ctx(otel_ctx:t()) -> opentelemetry:span_ctx() | undefined. -maybe_parent_span_ctx(Ctx) -> - case otel_tracer:current_span_ctx(Ctx) of - ActiveSpanCtx=#span_ctx{} -> - ActiveSpanCtx; - _ -> - otel_tracer:current_external_span_ctx() - %% otel_ctx:get_value(?EXTERNAL_SPAN_CTX) - end. - -spec with_span(opentelemetry:tracer(), opentelemetry:span_name(), otel_tracer:traced_fun()) -> ok. with_span(Tracer, SpanName, Fun) -> with_span(Tracer, SpanName, #{}, Fun). diff --git a/apps/opentelemetry/test/opentelemetry_SUITE.erl b/apps/opentelemetry/test/opentelemetry_SUITE.erl index be7b7363..086cb78f 100644 --- a/apps/opentelemetry/test/opentelemetry_SUITE.erl +++ b/apps/opentelemetry/test/opentelemetry_SUITE.erl @@ -9,6 +9,7 @@ -include_lib("opentelemetry_api/include/otel_tracer.hrl"). -include("otel_span.hrl"). -include("otel_test_utils.hrl"). +-include("otel_sampler.hrl"). all() -> [all_testcases(), @@ -17,7 +18,8 @@ all() -> all_testcases() -> [with_span, macros, child_spans, update_span_data, tracer_instrumentation_library, - tracer_previous_ctx, stop_temporary_app, reset_after, attach_ctx, default_sampler]. + tracer_previous_ctx, stop_temporary_app, reset_after, attach_ctx, default_sampler, + record_but_not_sample]. groups() -> [{w3c, [], [propagation]}, @@ -209,6 +211,9 @@ propagation(Config) -> span_id=SpanId} = ?start_span(<<"span-1">>), ?set_current_span(SpanCtx), + ?assertMatch(#span_ctx{trace_flags=1}, ?current_span_ctx), + ?assertMatch(#span_ctx{is_recording=true}, ?current_span_ctx), + otel_baggage:set("key-1", "value-1"), otel_baggage:set("key-2", "value-2"), Headers = otel_propagator:text_map_inject([{<<"existing-header">>, <<"I exist">>}]), @@ -241,11 +246,9 @@ propagation(Config) -> ?assertEqual(#{"key-1" => "value-1", "key-2" => "value-2"}, otel_baggage:get_all()), - %% extracted remote spans are not set to the active span - %% instead they are stored under a special "external span" - %% key and then used as the parent if current active span - %% is undefined or invalid - ?assertEqual(undefined, ?current_span_ctx), + %% extracted remote spans are set to the active span + %% but with `is_recording' false + ?assertMatch(#span_ctx{is_recording=false}, ?current_span_ctx), #span_ctx{trace_id=TraceId2, span_id=_SpanId2} = ?start_span(<<"span-2">>), @@ -366,8 +369,6 @@ stop_temporary_app(_Config) -> ok. default_sampler(_Config) -> - %% Tid = ?config(tid, Config), - Tracer = opentelemetry:get_tracer(), %% root span should be sampled by default sampler @@ -421,6 +422,39 @@ default_sampler(_Config) -> ok. +record_but_not_sample(Config) -> + ct:comment("Test that a Span that the sampler returns RECORD_ONLY for gets created" + "as a valid recorded span but is not sent to the exporter."), + Tid = ?config(tid, Config), + + Sampler = otel_sampler:setup(static_sampler, #{<<"span-record-and-sample">> => ?RECORD_AND_SAMPLE, + <<"span-record">> => ?RECORD_ONLY}), + + Tracer = opentelemetry:get_tracer(), + + SpanCtx1 = otel_tracer:start_span(Tracer, <<"span-record-and-sample">>, #{sampler => Sampler}), + ?assertEqual(true, SpanCtx1#span_ctx.is_recording), + ?assertEqual(1, SpanCtx1#span_ctx.trace_flags), + + ?set_current_span(SpanCtx1), + ?assertMatch(SpanCtx1, ?current_span_ctx), + + SpanCtx2 = otel_tracer:start_span(Tracer, <<"span-record">>, #{sampler => Sampler}), + ?assertEqual(true, SpanCtx2#span_ctx.is_recording), + ?assertEqual(0, SpanCtx2#span_ctx.trace_flags), + + ?set_current_span(SpanCtx2), + otel_span:end_span(SpanCtx2), + + otel_span:end_span(SpanCtx1), + + assert_all_exported(Tid, [SpanCtx1]), + + %% span-2 is recorded but not sampled, so should not show up in the export table + assert_not_exported(Tid, SpanCtx2), + + ok. + %% assert_all_exported(Tid, SpanCtxs) -> diff --git a/apps/opentelemetry/test/otel_samplers_SUITE.erl b/apps/opentelemetry/test/otel_samplers_SUITE.erl index 5e7d4ed1..c98ddef9 100644 --- a/apps/opentelemetry/test/otel_samplers_SUITE.erl +++ b/apps/opentelemetry/test/otel_samplers_SUITE.erl @@ -9,7 +9,7 @@ -include("otel_sampler.hrl"). all() -> - [trace_id_ratio_based, parent_based, get_description]. + [trace_id_ratio_based, parent_based, get_description, custom_sampler_module]. init_per_suite(Config) -> application:load(opentelemetry), @@ -44,34 +44,38 @@ trace_id_ratio_based(_Config) -> DoSample = 120647249294066572380176333851662846319, DoNotSample = 53020601517903903921384168845238205400, + Ctx = otel_ctx:new(), + %% sampler that runs on all spans {Sampler, _, Opts} = otel_sampler:setup(trace_id_ratio_based, Probability), %% checks the trace id is under the upper bound - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - Sampler(DoSample, undefined, [], SpanName, undefined, [], Opts)), + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, undefined), + DoSample, [], SpanName, undefined, [], Opts)), %% checks the trace id is is over the upper bound - ?assertMatch({?NOT_RECORD, [], []}, - Sampler(DoNotSample, undefined, [], SpanName, undefined, [], Opts)), + ?assertMatch({?DROP, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, undefined), + DoNotSample, [], SpanName, undefined, [], Opts)), %% ignores the parent span context trace flags - ?assertMatch({?NOT_RECORD, [], []}, - Sampler(DoNotSample, #span_ctx{trace_flags=1, - is_remote=true}, - [], SpanName, undefined, [], Opts)), + ?assertMatch({?DROP, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, #span_ctx{trace_flags=1, + is_remote=true}), + DoNotSample, [], SpanName, undefined, [], Opts)), %% ignores the parent span context trace flags - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - Sampler(DoSample, #span_ctx{trace_flags=0, - is_remote=false}, - [], SpanName, undefined, [], Opts)), + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, #span_ctx{trace_flags=0, + is_remote=false}), + DoSample, [], SpanName, undefined, [], Opts)), %% trace id is under the upper bound - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - Sampler(DoSample, #span_ctx{trace_flags=0, - is_remote=true}, - [], SpanName, undefined, [], Opts)), + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, #span_ctx{trace_flags=0, + is_remote=true}), + DoSample, [], SpanName, undefined, [], Opts)), ok. @@ -81,39 +85,53 @@ parent_based(_Config) -> DoSample = 120647249294066572380176333851662846319, DoNotSample = 53020601517903903921384168845238205400, + Ctx = otel_ctx:new(), + {Sampler, _, Opts} = otel_sampler:setup(parent_based, #{root => {trace_id_ratio_based, Probability}}), %% with no parent it will run the probability sampler - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - Sampler(DoSample, undefined, [], SpanName, undefined, [], Opts)), - ?assertMatch({?NOT_RECORD, [], []}, - Sampler(DoNotSample, undefined, [], SpanName, undefined, [], Opts)), + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, undefined), + DoSample, [], SpanName, undefined, [], Opts)), + ?assertMatch({?DROP, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, undefined), + DoNotSample, [], SpanName, undefined, [], Opts)), %% with parent it will use the parents value - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - Sampler(DoNotSample, #span_ctx{trace_flags=1, - is_remote=true}, - [], SpanName, undefined, [], Opts)), - ?assertMatch({?NOT_RECORD, [], []}, - Sampler(DoNotSample, #span_ctx{trace_flags=0, - is_remote=true}, - [], SpanName, undefined, [], Opts)), + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, #span_ctx{trace_flags=1, + is_remote=true}), + DoNotSample, [], SpanName, undefined, [], Opts)), + ?assertMatch({?DROP, [], []}, + Sampler(otel_tracer:set_current_span(Ctx, #span_ctx{trace_flags=0, + is_remote=true}), + DoNotSample, [], SpanName, undefined, [], Opts)), %% with no root sampler in setup opts the default sampler always_on is used {DefaultParentOrElse, _, Opts1} = otel_sampler:setup(parent_based, #{}), - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - DefaultParentOrElse(DoSample, undefined, [], SpanName, undefined, [], Opts1)), - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - DefaultParentOrElse(DoNotSample, undefined, [], SpanName, undefined, [], Opts1)), + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + DefaultParentOrElse(otel_tracer:set_current_span(Ctx, undefined), + DoSample, [], SpanName, undefined, [], Opts1)), + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + DefaultParentOrElse(otel_tracer:set_current_span(Ctx, undefined), + DoNotSample, [], SpanName, undefined, [], Opts1)), + + ?assertMatch({?RECORD_AND_SAMPLE, [], []}, + DefaultParentOrElse(otel_tracer:set_current_span(Ctx, #span_ctx{trace_flags=1}), + DoNotSample, [], SpanName, undefined, [], Opts1)), + ?assertMatch({?DROP, [], []}, + DefaultParentOrElse(otel_tracer:set_current_span(Ctx, #span_ctx{trace_flags=0, + is_remote=true}), + DoNotSample, [], SpanName, undefined, [], Opts1)), - ?assertMatch({?RECORD_AND_SAMPLED, [], []}, - DefaultParentOrElse(DoNotSample, #span_ctx{trace_flags=1}, - [], SpanName, undefined, [], Opts1)), - ?assertMatch({?NOT_RECORD, [], []}, - DefaultParentOrElse(DoNotSample, #span_ctx{trace_flags=0, - is_remote=true}, - [], SpanName, undefined, [], Opts1)), + ok. +custom_sampler_module(_Config) -> + SpanName = <<"span-name">>, + {Sampler, _, Opts} = otel_sampler:setup(static_sampler, #{SpanName => ?DROP}), + ?assertMatch({?DROP, [], []}, + Sampler(otel_ctx:new(), opentelemetry:generate_trace_id(), [], + SpanName, undefined, [], Opts)), ok. diff --git a/apps/opentelemetry/test/static_sampler.erl b/apps/opentelemetry/test/static_sampler.erl new file mode 100644 index 00000000..6d3cca55 --- /dev/null +++ b/apps/opentelemetry/test/static_sampler.erl @@ -0,0 +1,11 @@ +-module(static_sampler). + +-export([setup/1]). + +-include("otel_sampler.hrl"). + +%% sampler returns the value from the Opts map based on the SpanName or `NOT_RECORD' +setup(Opts) -> + {fun(_, _, _, SpanName, _, _, Opts1) -> + {maps:get(SpanName, Opts1, ?DROP), [], []} + end, <<"StaticSampler">>, Opts}. diff --git a/apps/opentelemetry_api/include/opentelemetry.hrl b/apps/opentelemetry_api/include/opentelemetry.hrl index 0dac8386..cd005852 100644 --- a/apps/opentelemetry_api/include/opentelemetry.hrl +++ b/apps/opentelemetry_api/include/opentelemetry.hrl @@ -22,7 +22,7 @@ %% is actually used (at the time of exporting). %% for use in guards: sampling bit is the first bit in 8-bit trace options --define(IS_SPAN_ENABLED(TraceOptions), (TraceOptions band 1) =/= 0). +-define(IS_SAMPLED(TraceFlags), (TraceFlags band 1) =/= 0). -define(SPAN_KIND_INTERNAL, 'INTERNAL'). -define(SPAN_KIND_SERVER, 'SERVER'). @@ -32,9 +32,9 @@ -record(span_ctx, { %% 128 bit int trace id - trace_id :: opentelemetry:trace_id() | undefined, + trace_id :: opentelemetry:trace_id(), %% 64 bit int span id - span_id :: opentelemetry:span_id() | undefined, + span_id :: opentelemetry:span_id(), %% 8-bit integer, lowest bit is if it is sampled trace_flags = 1 :: integer() | undefined, %% Tracestate represents tracing-system specific context in a list of key-value pairs. diff --git a/apps/opentelemetry_api/src/otel_tracer.erl b/apps/opentelemetry_api/src/otel_tracer.erl index 1eb9c83a..7cfcfd84 100644 --- a/apps/opentelemetry_api/src/otel_tracer.erl +++ b/apps/opentelemetry_api/src/otel_tracer.erl @@ -21,11 +21,11 @@ start_span/4, with_span/3, with_span/4, + non_recording_span/3, set_current_span/1, set_current_span/2, current_span_ctx/0, current_span_ctx/1, - current_external_span_ctx/0, text_map_propagators/1, end_span/0, set_attribute/2, @@ -38,8 +38,6 @@ -include("opentelemetry.hrl"). -define(CURRENT_SPAN_CTX, {?MODULE, span_ctx}). -%% the span context extracted with a propagator --define(EXTERNAL_SPAN_CTX, {?MODULE, external_span_ctx}). -define(is_recording(SpanCtx), SpanCtx =/= undefined andalso SpanCtx#span_ctx.is_recording =:= true). @@ -76,6 +74,16 @@ with_span(Tracer={Module, _}, SpanName, Fun) when is_atom(Module) -> with_span(Tracer={Module, _}, SpanName, Opts, Fun) when is_atom(Module) -> Module:with_span(Tracer, SpanName, Opts, Fun). +%% @doc Returns a `span_ctx' record with `is_recording' set to `false'. This is mainly +%% for use in propagators when they extract a Span to be used as a parent. +-spec non_recording_span(opentelemetry:trace_id(), opentelemetry:span_id(), opentelemetry:trace_flags()) + -> opentelemetry:span_ctx(). +non_recording_span(TraceId, SpanId, Traceflags) -> + #span_ctx{trace_id=TraceId, + span_id=SpanId, + is_recording=false, + trace_flags=Traceflags}. + -spec set_current_span(opentelemetry:span_ctx()) -> ok. set_current_span(SpanCtx) -> otel_ctx:set_value(?CURRENT_SPAN_CTX, SpanCtx). @@ -92,15 +100,12 @@ current_span_ctx() -> current_span_ctx(Ctx) -> otel_ctx:get_value(Ctx, ?CURRENT_SPAN_CTX, undefined). -current_external_span_ctx() -> - otel_ctx:get_value(?EXTERNAL_SPAN_CTX). - -spec text_map_propagators(module()) -> {otel_propagator:text_map_extractor(), otel_propagator:text_map_injector()}. text_map_propagators(Module) -> ToText = fun Module:inject/1, FromText = fun Module:extract/2, Injector = otel_ctx:text_map_injector(?CURRENT_SPAN_CTX, ToText), - Extractor = otel_ctx:text_map_extractor(?EXTERNAL_SPAN_CTX, FromText), + Extractor = otel_ctx:text_map_extractor(?CURRENT_SPAN_CTX, FromText), {Extractor, Injector}. %% Span operations diff --git a/apps/opentelemetry_exporter/src/opentelemetry_exporter.erl b/apps/opentelemetry_exporter/src/opentelemetry_exporter.erl index 8198b276..a4bb8071 100644 --- a/apps/opentelemetry_exporter/src/opentelemetry_exporter.erl +++ b/apps/opentelemetry_exporter/src/opentelemetry_exporter.erl @@ -116,7 +116,7 @@ to_proto(#span{trace_id=TraceId, events=TimedEvents, links=Links, status=Status, - trace_options=_TraceOptions, + trace_flags=_TraceFlags, is_recording=_IsRecording}) -> ParentSpanId = case MaybeParentSpanId of undefined -> <<>>; _ -> <> end, #{name => to_binary(Name),