From 9193a0fb33ed48fc479f6c642908a2a26cd5057f Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Wed, 3 Nov 2021 15:21:05 -0600 Subject: [PATCH 1/2] add simple span processor The Simple Span Processor does a blocking call when ending a span that returns after the span is exported. --- apps/opentelemetry/src/opentelemetry_sup.erl | 10 +- .../src/otel_batch_processor.erl | 110 +-------- apps/opentelemetry/src/otel_exporter.erl | 95 ++++++++ .../src/otel_simple_processor.erl | 223 ++++++++++++++++++ .../opentelemetry/src/otel_span_processor.erl | 2 +- apps/opentelemetry/src/otel_utils.erl | 28 +++ .../test/opentelemetry_SUITE.erl | 33 ++- 7 files changed, 394 insertions(+), 107 deletions(-) create mode 100644 apps/opentelemetry/src/otel_simple_processor.erl create mode 100644 apps/opentelemetry/src/otel_utils.erl diff --git a/apps/opentelemetry/src/opentelemetry_sup.erl b/apps/opentelemetry/src/opentelemetry_sup.erl index c7d9bbf5..a8c21b7a 100644 --- a/apps/opentelemetry/src/opentelemetry_sup.erl +++ b/apps/opentelemetry/src/opentelemetry_sup.erl @@ -57,6 +57,14 @@ init([Opts]) -> type => worker, modules => [otel_batch_processor]}, + SimpleProcessorOpts = proplists:get_value(otel_simple_processor, Processors, #{}), + SimpleProcessor = #{id => otel_simple_processor, + start => {otel_simple_processor, start_link, [SimpleProcessorOpts]}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [otel_simple_processor]}, + SpanSup = #{id => otel_span_sup, start => {otel_span_sup, start_link, [Opts]}, type => supervisor, @@ -67,6 +75,6 @@ init([Opts]) -> %% `TracerServer' *must* start before the `BatchProcessor' %% `BatchProcessor' relies on getting the `Resource' from %% the `TracerServer' process - ChildSpecs = [Detectors, TracerServer, BatchProcessor, SpanSup], + ChildSpecs = [Detectors, TracerServer, BatchProcessor, SimpleProcessor, SpanSup], {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/opentelemetry/src/otel_batch_processor.erl b/apps/opentelemetry/src/otel_batch_processor.erl index 790987b2..ac6c1bed 100644 --- a/apps/opentelemetry/src/otel_batch_processor.erl +++ b/apps/opentelemetry/src/otel_batch_processor.erl @@ -12,11 +12,9 @@ %% See the License for the specific language governing permissions and %% limitations under the License. %% -%% @doc This module has the behaviour that each exporter must implement -%% and creates the buffer of trace spans to be exported. -%% -%% The exporter process can be configured to export the current finished -%% spans based on timeouts and the size of the finished spans table. +%% @doc The Batch Span Processor implements the `otel_span_processor' +%% behaviour. It stores finished Spans in a ETS table buffer and exports +%% them on an interval or when the table reaches a maximum size. %% %% Timeouts: %% exporting_timeout_ms: How long to let the exports run before killing. @@ -109,7 +107,7 @@ init([Args]) -> ScheduledDelay = maps:get(scheduled_delay_ms, Args, ?DEFAULT_SCHEDULED_DELAY_MS), CheckTableSize = maps:get(check_table_size_ms, Args, ?DEFAULT_CHECK_TABLE_SIZE_MS), - Exporter = init_exporter(maps:get(exporter, Args, undefined)), + Exporter = otel_exporter:init(maps:get(exporter, Args, undefined)), Resource = otel_tracer_provider:resource(), _Tid1 = new_export_table(?TABLE_1), @@ -186,8 +184,8 @@ handle_event_(_State, {timeout, check_table_size}, check_table_size, #data{max_q keep_state_and_data end; handle_event_(_, {call, From}, {set_exporter, Exporter}, Data=#data{exporter=OldExporter}) -> - shutdown_exporter(OldExporter), - {keep_state, Data#data{exporter=init_exporter(Exporter)}, [{reply, From, ok}]}; + otel_exporter:shutdown(OldExporter), + {keep_state, Data#data{exporter=otel_exporter:init(Exporter)}, [{reply, From, ok}]}; handle_event_(_, _, _, _) -> keep_state_and_data. @@ -244,76 +242,6 @@ new_export_table(Name) -> %% for each instrumentation_library and export together. {keypos, #span.instrumentation_library}]). -init_exporter(undefined) -> - undefined; -init_exporter({ExporterModule, Config}) when is_atom(ExporterModule) -> - try ExporterModule:init(Config) of - {ok, ExporterConfig} -> - {ExporterModule, ExporterConfig}; - ignore -> - undefined - catch - Kind:Reason:StackTrace -> - %% logging in debug level since config argument in stacktrace could have secrets - ?LOG_DEBUG(#{source => exporter, - during => init, - kind => Kind, - reason => Reason, - exporter => ExporterModule, - stacktrace => StackTrace}, #{report_cb => fun ?MODULE:report_cb/1}), - - %% print a more useful message about the failure if we can discern - %% one from the failure reason and exporter used - case {Kind, Reason} of - {error, badarg} when ExporterModule =:= opentelemetry_exporter -> - case maps:get(protocol, Config, undefined) of - grpc -> - %% grpc protocol uses grpcbox which is not included by default - %% this will check if it is available so we can warn the user if - %% the dependency needs to be added - try grpcbox:module_info() of - _ -> - undefined - catch - _:_ -> - ?LOG_WARNING("OTLP tracer, ~p, failed to initialize when using GRPC protocol and `grpcbox` module is not available in the code path. Verify that you have the `grpcbox` dependency included and rerun.", [ExporterModule]), - undefined - end; - _ -> - %% same as the debug log above - %% without the stacktrace and at a higher level - ?LOG_WARNING(#{source => exporter, - during => init, - kind => Kind, - reason => Reason, - exporter => ExporterModule}, #{report_cb => fun ?MODULE:report_cb/1}), - undefined - end; - {error, undef} when ExporterModule =:= opentelemetry_exporter -> - ?LOG_WARNING("Trace exporter module ~p not found. Verify you have included the `opentelemetry_exporter` dependency.", [ExporterModule]), - undefined; - {error, undef} -> - ?LOG_WARNING("Trace exporter module ~p not found. Verify you have included the dependency that contains the exporter module.", [ExporterModule]), - undefined; - _ -> - %% same as the debug log above - %% without the stacktrace and at a higher level - ?LOG_WARNING(#{source => exporter, - during => init, - kind => Kind, - reason => Reason, - exporter => ExporterModule}, #{report_cb => fun ?MODULE:report_cb/1}), - undefined - end - end; -init_exporter(ExporterModule) when is_atom(ExporterModule) -> - init_exporter({ExporterModule, []}). - -shutdown_exporter(undefined) -> - ok; -shutdown_exporter({ExporterModule, Config}) -> - ExporterModule:shutdown(Config). - export_spans(#data{exporter=Exporter, resource=Resource}) -> CurrentTable = ?CURRENT_TABLE, @@ -354,7 +282,7 @@ export({ExporterModule, Config}, Resource, SpansTid) -> %% don't let a exporter exception crash us %% and return true if exporter failed try - ExporterModule:export(SpansTid, Resource, Config) =:= failed_not_retryable + otel_exporter:export(ExporterModule, SpansTid, Resource, Config) =:= failed_not_retryable catch Kind:Reason:StackTrace -> ?LOG_INFO(#{source => exporter, @@ -367,20 +295,6 @@ export({ExporterModule, Config}, Resource, SpansTid) -> end. %% logger format functions -report_cb(#{source := exporter, - during := init, - kind := Kind, - reason := Reason, - exporter := ExporterModule, - stacktrace := StackTrace}) -> - {"OTLP tracer ~p failed to initialize: ~ts", - [ExporterModule, format_exception(Kind, Reason, StackTrace)]}; -report_cb(#{source := exporter, - during := init, - kind := Kind, - reason := Reason, - exporter := ExporterModule}) -> - {"OTLP tracer ~p failed to initialize with exception ~p:~p", [ExporterModule, Kind, Reason]}; report_cb(#{source := exporter, during := export, kind := Kind, @@ -388,12 +302,4 @@ report_cb(#{source := exporter, exporter := ExporterModule, stacktrace := StackTrace}) -> {"exporter threw exception: exporter=~p ~ts", - [ExporterModule, format_exception(Kind, Reason, StackTrace)]}. - --if(?OTP_RELEASE >= 24). -format_exception(Kind, Reason, StackTrace) -> - erl_error:format_exception(Kind, Reason, StackTrace). --else. -format_exception(Kind, Reason, StackTrace) -> - io_lib:format("~p:~p ~p", [Kind, Reason, StackTrace]). --endif. + [ExporterModule, otel_utils:format_exception(Kind, Reason, StackTrace)]}. diff --git a/apps/opentelemetry/src/otel_exporter.erl b/apps/opentelemetry/src/otel_exporter.erl index 3bb588d7..64d6b975 100644 --- a/apps/opentelemetry/src/otel_exporter.erl +++ b/apps/opentelemetry/src/otel_exporter.erl @@ -17,6 +17,11 @@ %%%----------------------------------------------------------------------- -module(otel_exporter). +-export([init/1, + export/4, + shutdown/1, + report_cb/1]). + %% Do any initialization of the exporter here and return configuration %% that will be passed along with a list of spans to the `export' function. -callback init(term()) -> {ok, term()} | ignore. @@ -30,3 +35,93 @@ failed_not_retryable | failed_retryable. -callback shutdown(term()) -> ok. + +-include_lib("kernel/include/logger.hrl"). + +init(undefined) -> + undefined; +init({ExporterModule, Config}) when is_atom(ExporterModule) -> + try ExporterModule:init(Config) of + {ok, ExporterConfig} -> + {ExporterModule, ExporterConfig}; + ignore -> + undefined + catch + Kind:Reason:StackTrace -> + %% logging in debug level since config argument in stacktrace could have secrets + ?LOG_DEBUG(#{source => exporter, + during => init, + kind => Kind, + reason => Reason, + exporter => ExporterModule, + stacktrace => StackTrace}, #{report_cb => fun ?MODULE:report_cb/1}), + + %% print a more useful message about the failure if we can discern + %% one from the failure reason and exporter used + case {Kind, Reason} of + {error, badarg} when ExporterModule =:= opentelemetry_exporter -> + case maps:get(protocol, Config, undefined) of + grpc -> + %% grpc protocol uses grpcbox which is not included by default + %% this will check if it is available so we can warn the user if + %% the dependency needs to be added + try grpcbox:module_info() of + _ -> + undefined + catch + _:_ -> + ?LOG_WARNING("OTLP tracer, ~p, failed to initialize when using GRPC protocol and `grpcbox` module is not available in the code path. Verify that you have the `grpcbox` dependency included and rerun.", [ExporterModule]), + undefined + end; + _ -> + %% same as the debug log above + %% without the stacktrace and at a higher level + ?LOG_WARNING(#{source => exporter, + during => init, + kind => Kind, + reason => Reason, + exporter => ExporterModule}, #{report_cb => fun ?MODULE:report_cb/1}), + undefined + end; + {error, undef} when ExporterModule =:= opentelemetry_exporter -> + ?LOG_WARNING("Trace exporter module ~p not found. Verify you have included the `opentelemetry_exporter` dependency.", [ExporterModule]), + undefined; + {error, undef} -> + ?LOG_WARNING("Trace exporter module ~p not found. Verify you have included the dependency that contains the exporter module.", [ExporterModule]), + undefined; + _ -> + %% same as the debug log above + %% without the stacktrace and at a higher level + ?LOG_WARNING(#{source => exporter, + during => init, + kind => Kind, + reason => Reason, + exporter => ExporterModule}, #{report_cb => fun ?MODULE:report_cb/1}), + undefined + end + end; +init(ExporterModule) when is_atom(ExporterModule) -> + init({ExporterModule, []}). + +export(ExporterModule, SpansTid, Resource, Config) -> + ExporterModule:export(SpansTid, Resource, Config). + +shutdown(undefined) -> + ok; +shutdown({ExporterModule, Config}) -> + ExporterModule:shutdown(Config). + +report_cb(#{source := exporter, + during := init, + kind := Kind, + reason := Reason, + exporter := ExporterModule, + stacktrace := StackTrace}) -> + {"OTLP tracer ~p failed to initialize: ~ts", + [ExporterModule, otel_utils:format_exception(Kind, Reason, StackTrace)]}; +report_cb(#{source := exporter, + during := init, + kind := Kind, + reason := Reason, + exporter := ExporterModule}) -> + {"OTLP tracer ~p failed to initialize with exception ~p:~p", [ExporterModule, Kind, Reason]}. diff --git a/apps/opentelemetry/src/otel_simple_processor.erl b/apps/opentelemetry/src/otel_simple_processor.erl new file mode 100644 index 00000000..cdff5071 --- /dev/null +++ b/apps/opentelemetry/src/otel_simple_processor.erl @@ -0,0 +1,223 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2021, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc This Span Processor synchronously exports each ended Span. +%% +%% Use this processor if ending a Span should block until it has been +%% exported. This is useful for cases like a serverless environment where +%% the application will possibly be suspended after handling a request. +%% +%% @end +%%%----------------------------------------------------------------------- +-module(otel_simple_processor). + +-behaviour(gen_statem). +-behaviour(otel_span_processor). + +-export([start_link/1, + on_start/3, + on_end/2, + force_flush/1, + set_exporter/1, + set_exporter/2, + report_cb/1]). + +-export([init/1, + callback_mode/0, + idle/3, + exporting/3, + terminate/3]). + +-include_lib("opentelemetry_api/include/opentelemetry.hrl"). +-include_lib("kernel/include/logger.hrl"). +-include("otel_span.hrl"). + +-record(data, {exporter :: {module(), term()} | undefined, + current_from :: gen_statem:from() | undefined, + resource :: otel_resource:t(), + handed_off_table :: atom() | undefined, + runner_pid :: pid() | undefined, + exporting_timeout_ms :: integer()}). + +-define(DEFAULT_EXPORTER_TIMEOUT_MS, timer:minutes(5)). + +start_link(Opts) -> + gen_statem:start_link({local, ?MODULE}, ?MODULE, [Opts], []). + +%% @equiv set_exporter(Exporter, []) +set_exporter(Exporter) -> + set_exporter(Exporter, []). + +%% @doc Sets the batch exporter `Exporter'. +-spec set_exporter(module(), term()) -> ok. +set_exporter(Exporter, Options) -> + gen_statem:call(?MODULE, {set_exporter, {Exporter, Options}}). + +-spec on_start(otel_ctx:t(), opentelemetry:span(), otel_span_processor:processor_config()) + -> opentelemetry:span(). +on_start(_Ctx, Span, _) -> + 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{}, _) -> + gen_statem:call(?MODULE, {export, Span}); +on_end(_Span, _) -> + {error, invalid_span}. + +-spec force_flush(otel_span_processor:processor_config()) -> ok. +force_flush(_) -> + gen_statem:cast(?MODULE, force_flush). + +init([Args]) -> + process_flag(trap_exit, true), + + ExportingTimeout = maps:get(exporting_timeout_ms, Args, ?DEFAULT_EXPORTER_TIMEOUT_MS), + + Exporter = otel_exporter:init(maps:get(exporter, Args, undefined)), + Resource = otel_tracer_provider:resource(), + + {ok, idle, #data{exporter=Exporter, + resource = Resource, + handed_off_table=undefined, + exporting_timeout_ms=ExportingTimeout}}. + +callback_mode() -> + state_functions. + +idle({call, From}, {export, Span}, Data) -> + {next_state, exporting, Data, [{next_event, internal, {export, From, Span}}]}; +idle(EventType, Event, Data) -> + handle_event_(idle, EventType, Event, Data). + +exporting({call, _From}, {export, _}, _) -> + {keep_state_and_data, [postpone]}; +exporting(internal, {export, From, Span}, Data=#data{exporting_timeout_ms=ExportingTimeout}) -> + {OldTableName, RunnerPid} = export_span(Span, Data), + {keep_state, Data#data{runner_pid=RunnerPid, + current_from=From, + handed_off_table=OldTableName}, + [{state_timeout, ExportingTimeout, exporting_timeout}]}; +exporting(state_timeout, exporting_timeout, Data=#data{current_from=From, + handed_off_table=_ExportingTable}) -> + %% kill current exporting process because it is taking too long + %% which deletes the exporting table, so create a new one and + %% repeat the state to force another span exporting immediately + Data1 = kill_runner(Data), + {repeat_state, Data1, [{reply, From, {error, timeout}}]}; +%% important to verify runner_pid and FromPid are the same in case it was sent +%% after kill_runner was called but before it had done the unlink +exporting(info, {'EXIT', FromPid, _}, Data=#data{runner_pid=FromPid}) -> + complete_exporting(Data); +%% important to verify runner_pid and FromPid are the same in case it was sent +%% after kill_runner was called but before it had done the unlink +exporting(info, {completed, FromPid}, Data=#data{runner_pid=FromPid}) -> + complete_exporting(Data); +exporting(EventType, Event, Data) -> + handle_event_(exporting, EventType, Event, Data). + +handle_event_(_, {call, From}, {set_exporter, Exporter}, Data=#data{exporter=OldExporter}) -> + otel_exporter:shutdown(OldExporter), + {keep_state, Data#data{exporter=otel_exporter:init(Exporter)}, [{reply, From, ok}]}; +handle_event_(_, _, _, _) -> + keep_state_and_data. + +terminate(_, _, _Data) -> + ok. + +%% + +complete_exporting(Data=#data{current_from=From, + handed_off_table=ExportingTable}) + when ExportingTable =/= undefined -> + {next_state, idle, Data#data{current_from=undefined, + runner_pid=undefined, + handed_off_table=undefined}, + [{reply, From, ok}]}. + +kill_runner(Data=#data{runner_pid=RunnerPid}) -> + erlang:unlink(RunnerPid), + erlang:exit(RunnerPid, kill), + Data#data{runner_pid=undefined, + handed_off_table=undefined}. + +new_export_table(Name) -> + ets:new(Name, [public, + {write_concurrency, true}, + duplicate_bag, + %% OpenTelemetry exporter protos group by the + %% instrumentation_library. So using instrumentation_library + %% as the key means we can easily lookup all spans for + %% for each instrumentation_library and export together. + {keypos, #span.instrumentation_library}]). + +export_span(Span, #data{exporter=Exporter, + resource=Resource}) -> + Table = new_export_table(otel_simple_processor_table), + _ = ets:insert(Table, Span), + Self = self(), + RunnerPid = erlang:spawn_link(fun() -> send_spans(Self, Resource, Exporter) end), + ets:give_away(Table, RunnerPid, export), + {Table, RunnerPid}. + +%% Additional benefit of using a separate process is calls to `register` won't +%% timeout if the actual exporting takes longer than the call timeout +send_spans(FromPid, Resource, Exporter) -> + receive + {'ETS-TRANSFER', Table, FromPid, export} -> + export(Exporter, Resource, Table), + ets:delete(Table), + completed(FromPid) + end. + +completed(FromPid) -> + FromPid ! {completed, self()}. + +export(undefined, _, _) -> + true; +export({ExporterModule, Config}, Resource, SpansTid) -> + %% don't let a exporter exception crash us + %% and return true if exporter failed + try + ExporterModule:export(SpansTid, Resource, Config) =:= failed_not_retryable + catch + Kind:Reason:StackTrace -> + ?LOG_INFO(#{source => exporter, + during => export, + kind => Kind, + reason => Reason, + exporter => ExporterModule, + stacktrace => StackTrace}, #{report_cb => fun ?MODULE:report_cb/1}), + true + end. + +%% logger format functions +report_cb(#{source := exporter, + during := export, + kind := Kind, + reason := Reason, + exporter := ExporterModule, + stacktrace := StackTrace}) -> + {"exporter threw exception: exporter=~p ~ts", + [ExporterModule, format_exception(Kind, Reason, StackTrace)]}. + +-if(?OTP_RELEASE >= 24). +format_exception(Kind, Reason, StackTrace) -> + erl_error:format_exception(Kind, Reason, StackTrace). +-else. +format_exception(Kind, Reason, StackTrace) -> + io_lib:format("~p:~p ~p", [Kind, Reason, StackTrace]). +-endif. diff --git a/apps/opentelemetry/src/otel_span_processor.erl b/apps/opentelemetry/src/otel_span_processor.erl index 19dd7d13..fe87cff4 100644 --- a/apps/opentelemetry/src/otel_span_processor.erl +++ b/apps/opentelemetry/src/otel_span_processor.erl @@ -12,7 +12,7 @@ %% See the License for the specific language governing permissions and %% limitations under the License. %% -%% @doc +%% @doc Behaviour each Span Processor must implement. %% @end %%%------------------------------------------------------------------------- -module(otel_span_processor). diff --git a/apps/opentelemetry/src/otel_utils.erl b/apps/opentelemetry/src/otel_utils.erl new file mode 100644 index 00000000..9e6e5325 --- /dev/null +++ b/apps/opentelemetry/src/otel_utils.erl @@ -0,0 +1,28 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2021, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%----------------------------------------------------------------------- +-module(otel_utils). + +-export([format_exception/3]). + +-if(?OTP_RELEASE >= 24). +format_exception(Kind, Reason, StackTrace) -> + erl_error:format_exception(Kind, Reason, StackTrace). +-else. +format_exception(Kind, Reason, StackTrace) -> + io_lib:format("~p:~p ~p", [Kind, Reason, StackTrace]). +-endif. diff --git a/apps/opentelemetry/test/opentelemetry_SUITE.erl b/apps/opentelemetry/test/opentelemetry_SUITE.erl index cff7ac34..e3255095 100644 --- a/apps/opentelemetry/test/opentelemetry_SUITE.erl +++ b/apps/opentelemetry/test/opentelemetry_SUITE.erl @@ -13,14 +13,30 @@ -include("otel_sampler.hrl"). -include("otel_span_ets.hrl"). + all() -> - [disable_auto_registration, registered_tracers, with_span, macros, child_spans, + [%% no need to include tests that don't export any spans with the simple/batch groups + disable_auto_registration, + registered_tracers, + %% force flush is a test of flushing the batch processor's table + force_flush, + + %% all other tests are run with both the simple and batch processor + {group, otel_simple_processor}, + {group, otel_batch_processor}]. + +all_cases() -> + [with_span, macros, child_spans, update_span_data, tracer_instrumentation_library, tracer_previous_ctx, stop_temporary_app, reset_after, attach_ctx, default_sampler, non_recording_ets_table, root_span_sampling_always_on, root_span_sampling_always_off, record_but_not_sample, record_exception_works, record_exception_with_message_works, propagator_configuration, propagator_configuration_with_os_env, force_flush]. +groups() -> + [{otel_simple_processor, [], all_cases()}, + {otel_batch_processor, [], all_cases()}]. + init_per_suite(Config) -> application:load(opentelemetry), Config. @@ -29,10 +45,20 @@ end_per_suite(_Config) -> application:unload(opentelemetry), ok. +init_per_group(Processor, Config) -> + [{processor, Processor} | Config]. + +end_per_group(_, _Config) -> + ok. + init_per_testcase(disable_auto_registration, Config) -> application:set_env(opentelemetry, register_loaded_applications, false), {ok, _} = application:ensure_all_started(opentelemetry), Config; +init_per_testcase(registered_tracers, Config) -> + application:set_env(opentelemetry, register_loaded_applications, true), + {ok, _} = application:ensure_all_started(opentelemetry), + Config; init_per_testcase(propagator_configuration, Config) -> os:unsetenv("OTEL_PROPAGATORS"), application:set_env(opentelemetry, text_map_propagators, [b3multi, baggage]), @@ -52,12 +78,13 @@ init_per_testcase(force_flush, Config) -> otel_batch_processor:set_exporter(otel_exporter_tab, Tid), [{tid, Tid} | Config]; init_per_testcase(_, Config) -> - application:set_env(opentelemetry, processors, [{otel_batch_processor, #{scheduled_delay_ms => 1}}]), + Processor = ?config(processor, Config), + application:set_env(opentelemetry, processors, [{Processor, #{scheduled_delay_ms => 1}}]), {ok, _} = application:ensure_all_started(opentelemetry), %% adds an exporter for a new table %% spans will be exported to a separate table for each of the test cases Tid = ets:new(exported_spans, [public, bag]), - otel_batch_processor:set_exporter(otel_exporter_tab, Tid), + Processor:set_exporter(otel_exporter_tab, Tid), [{tid, Tid} | Config]. end_per_testcase(disable_auto_registration, _Config) -> From 49b563c7dc1cd6d424dab35d86ef2c6495f0cec6 Mon Sep 17 00:00:00 2001 From: Tristan Sloughter Date: Fri, 5 Nov 2021 17:03:27 -0600 Subject: [PATCH 2/2] simple_processor: transition to idle after exporter timeout Since the simple_processor is only exporting a single span and does so based on a blocking call when a span ends there is no need to do a `repeat_state` after the exporting time out fails. The next export will be done if there is a span end message in the processor's queue. --- apps/opentelemetry/src/otel_simple_processor.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opentelemetry/src/otel_simple_processor.erl b/apps/opentelemetry/src/otel_simple_processor.erl index cdff5071..5fa7af55 100644 --- a/apps/opentelemetry/src/otel_simple_processor.erl +++ b/apps/opentelemetry/src/otel_simple_processor.erl @@ -117,7 +117,7 @@ exporting(state_timeout, exporting_timeout, Data=#data{current_from=From, %% which deletes the exporting table, so create a new one and %% repeat the state to force another span exporting immediately Data1 = kill_runner(Data), - {repeat_state, Data1, [{reply, From, {error, timeout}}]}; + {next_state, idle, Data1, [{reply, From, {error, timeout}}]}; %% important to verify runner_pid and FromPid are the same in case it was sent %% after kill_runner was called but before it had done the unlink exporting(info, {'EXIT', FromPid, _}, Data=#data{runner_pid=FromPid}) ->