diff --git a/README.md b/README.md index dd594f28..d128257b 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,6 @@ Approvers ([@open-telemetry/erlang-approvers](https://github.com/orgs/open-telem - [Tristan Sloughter](https://github.com/tsloughter), [Splunk](https://www.splunk.com/en_us/observability/apm-application-performance-monitoring.html) - [Bryan Naegele](https://github.com/bryannaegele), [SimpleBet](https://simplebet.io/) - [Greg Mefford](https://github.com/GregMefford), [STORD](https://www.stord.com/) -- [Zach Daniel](https://github.com/zachdaniel), - [Fred Hebert](https://github.com/ferd), [Honeycomb](https://www.honeycomb.io/) - [Ɓukasz Jan Niemier](https://github.com/hauleth) - [Iliia Khaprov](https://github.com/deadtrickster), [VMWare](https://www.vmware.com/) diff --git a/apps/opentelemetry/src/otel_batch_processor.erl b/apps/opentelemetry/src/otel_batch_processor.erl index 2d862412..0eae62cd 100644 --- a/apps/opentelemetry/src/otel_batch_processor.erl +++ b/apps/opentelemetry/src/otel_batch_processor.erl @@ -49,6 +49,7 @@ -include("otel_span.hrl"). -record(data, {exporter :: {module(), term()} | undefined, + exporter_config :: {module(), term()} | undefined, resource :: otel_resource:t(), handed_off_table :: atom() | undefined, runner_pid :: pid() | undefined, @@ -107,7 +108,6 @@ 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 = otel_exporter:init(maps:get(exporter, Args, undefined)), Resource = otel_tracer_provider:resource(), _Tid1 = new_export_table(?TABLE_1), @@ -116,7 +116,8 @@ init([Args]) -> enable(), - {ok, idle, #data{exporter=Exporter, + {ok, idle, #data{exporter=undefined, + exporter_config=maps:get(exporter, Args, undefined), resource = Resource, handed_off_table=undefined, max_queue_size=case SizeLimit of @@ -130,8 +131,17 @@ init([Args]) -> callback_mode() -> [state_functions, state_enter]. +idle(enter, _OldState, Data=#data{exporter=undefined, + exporter_config=ExporterConfig, + scheduled_delay_ms=SendInterval}) -> + Exporter = otel_exporter:init(ExporterConfig), + {keep_state, Data#data{exporter=Exporter}, [{{timeout, export_spans}, SendInterval, export_spans}]}; idle(enter, _OldState, #data{scheduled_delay_ms=SendInterval}) -> {keep_state_and_data, [{{timeout, export_spans}, SendInterval, export_spans}]}; +idle(_, export_spans, Data=#data{exporter=undefined, + exporter_config=ExporterConfig}) -> + Exporter = otel_exporter:init(ExporterConfig), + {next_state, exporting, Data#data{exporter=Exporter}}; idle(_, export_spans, Data) -> {next_state, exporting, Data}; idle(EventType, Event, Data) -> @@ -142,6 +152,9 @@ idle(EventType, Event, Data) -> %% after exporting({timeout, export_spans}, export_spans, _) -> {keep_state_and_data, [postpone]}; +exporting(enter, _OldState, #data{exporter=undefined}) -> + %% exporter still undefined, go back to idle + {keep_state_and_data, [{state_timeout, 0, no_exporter}]}; exporting(enter, _OldState, Data=#data{exporting_timeout_ms=ExportingTimeout, scheduled_delay_ms=SendInterval}) -> case export_spans(Data) of @@ -155,8 +168,13 @@ exporting(enter, _OldState, Data=#data{exporting_timeout_ms=ExportingTimeout, [{state_timeout, ExportingTimeout, exporting_timeout}, {{timeout, export_spans}, SendInterval, export_spans}]} end; + +%% two hacks since we can't transition to a new state or send an action from `enter' +exporting(state_timeout, no_exporter, Data) -> + {next_state, idle, Data}; exporting(state_timeout, empty_table, Data) -> {next_state, idle, Data}; + exporting(state_timeout, exporting_timeout, Data=#data{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 @@ -194,9 +212,15 @@ handle_event_(_State, {timeout, check_table_size}, check_table_size, #data{max_q enable(), keep_state_and_data end; -handle_event_(_, {call, From}, {set_exporter, Exporter}, Data=#data{exporter=OldExporter}) -> +handle_event_(_, {call, From}, {set_exporter, ExporterConfig}, Data=#data{exporter=OldExporter}) -> otel_exporter:shutdown(OldExporter), - {keep_state, Data#data{exporter=otel_exporter:init(Exporter)}, [{reply, From, ok}]}; + {keep_state, Data#data{exporter=undefined, + exporter_config=ExporterConfig}, [{reply, From, ok}, + {next_event, internal, init_exporter}]}; +handle_event_(_, internal, init_exporter, Data=#data{exporter=undefined, + exporter_config=ExporterConfig}) -> + Exporter = otel_exporter:init(ExporterConfig), + {keep_state, Data#data{exporter=Exporter}}; handle_event_(_, _, _, _) -> keep_state_and_data. diff --git a/apps/opentelemetry/src/otel_exporter.erl b/apps/opentelemetry/src/otel_exporter.erl index 64d6b975..72644924 100644 --- a/apps/opentelemetry/src/otel_exporter.erl +++ b/apps/opentelemetry/src/otel_exporter.erl @@ -42,8 +42,13 @@ init(undefined) -> undefined; init({ExporterModule, Config}) when is_atom(ExporterModule) -> try ExporterModule:init(Config) of - {ok, ExporterConfig} -> - {ExporterModule, ExporterConfig}; + {ok, ExporterState} when ExporterModule =:= opentelemetry_exporter -> + %% since we log when the initialization failed so the user knows it later succeeded + ?LOG_INFO("OTLP exporter successfully initialized"), + {ExporterModule, ExporterState}; + {ok, ExporterState} -> + ?LOG_INFO("Exporter ~tp successfully initialized", [ExporterModule]), + {ExporterModule, ExporterState}; ignore -> undefined catch @@ -70,7 +75,10 @@ init({ExporterModule, Config}) when is_atom(ExporterModule) -> 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]), + ?LOG_WARNING("OTLP exporter failed to initialize when using the GRPC " + "protocol and `grpcbox` module is not available in the " + "code path. Verify that you have the `grpcbox` dependency " + "included and rerun.", []), undefined end; _ -> @@ -84,10 +92,13 @@ init({ExporterModule, Config}) when is_atom(ExporterModule) -> 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]), + ?LOG_WARNING("OTLP exporter module `opentelemetry_exporter` 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]), + ?LOG_WARNING("Exporter module ~tp not found. Verify you have included " + "the dependency that contains the exporter module.", [ExporterModule]), undefined; _ -> %% same as the debug log above @@ -111,17 +122,31 @@ shutdown(undefined) -> shutdown({ExporterModule, Config}) -> ExporterModule:shutdown(Config). +report_cb(#{source := exporter, + during := init, + kind := Kind, + reason := Reason, + exporter := opentelemetry_exporter, + stacktrace := StackTrace}) -> + {"OTLP exporter failed to initialize: ~ts", + [otel_utils:format_exception(Kind, Reason, StackTrace)]}; report_cb(#{source := exporter, during := init, kind := Kind, reason := Reason, exporter := ExporterModule, stacktrace := StackTrace}) -> - {"OTLP tracer ~p failed to initialize: ~ts", + {"Exporter ~tp failed to initialize: ~ts", [ExporterModule, otel_utils:format_exception(Kind, Reason, StackTrace)]}; +report_cb(#{source := exporter, + during := init, + kind := Kind, + reason := Reason, + exporter := opentelemetry_exporter}) -> + {"OTLP exporter failed to initialize with exception ~tp:~tp", [Kind, Reason]}; 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]}. + {"Exporter ~p failed to initialize with exception ~tp:~tp", [ExporterModule, Kind, Reason]}. diff --git a/apps/opentelemetry/src/otel_simple_processor.erl b/apps/opentelemetry/src/otel_simple_processor.erl index 5fa7af55..c646cfc4 100644 --- a/apps/opentelemetry/src/otel_simple_processor.erl +++ b/apps/opentelemetry/src/otel_simple_processor.erl @@ -44,6 +44,7 @@ -include("otel_span.hrl"). -record(data, {exporter :: {module(), term()} | undefined, + exporter_config :: {module(), term()} | undefined, current_from :: gen_statem:from() | undefined, resource :: otel_resource:t(), handed_off_table :: atom() | undefined, @@ -87,13 +88,13 @@ init([Args]) -> 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, + {ok, idle, #data{exporter=undefined, + exporter_config=maps:get(exporter, Args, undefined), resource = Resource, handed_off_table=undefined, - exporting_timeout_ms=ExportingTimeout}}. + exporting_timeout_ms=ExportingTimeout}, [{next_event, internal, init_exporter}]}. callback_mode() -> state_functions. @@ -129,6 +130,10 @@ exporting(info, {completed, FromPid}, Data=#data{runner_pid=FromPid}) -> exporting(EventType, Event, Data) -> handle_event_(exporting, EventType, Event, Data). +handle_event_(_, internal, init_exporter, Data=#data{exporter=undefined, + exporter_config=ExporterConfig}) -> + Exporter = otel_exporter:init(ExporterConfig), + {keep_state, Data#data{exporter=Exporter}}; 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}]};