diff --git a/apps/opentelemetry/src/otel_configuration.erl b/apps/opentelemetry/src/otel_configuration.erl index 2b652dd6..4e357858 100644 --- a/apps/opentelemetry/src/otel_configuration.erl +++ b/apps/opentelemetry/src/otel_configuration.erl @@ -199,32 +199,23 @@ merge_processor_config(otel_simple_processor, Opts, ConfigMap, AppEnv) -> merge_processor_config(_, Opts, _, _) -> Opts. -merge_processor_config_(EnvMapping, Opts, ConfigMap, AppEnv) -> - Mappings = config_mappings(general_sdk), - +merge_processor_config_(EnvMapping, Opts, ConfigMap, _AppEnv) -> lists:foldl(fun({K, V}, Acc) -> case maps:get(K, ConfigMap, undefined) of undefined -> Acc; Value -> - %% use default only if the config isn't found in Opts - %% but if the value in ConfigMap isn't the default use it - IsDefault = is_default(K, AppEnv, Mappings), - case (IsDefault andalso not maps:is_key(V, Opts)) orelse not IsDefault of + %% apply top-level/env config value only if the + %% key isn't already explicitly set in processor opts + case maps:is_key(V, Opts) of true -> - Acc#{V => Value}; + Acc; false -> - Acc + Acc#{V => Value} end end end, Opts, EnvMapping). -%% return true if the user (through application or os environment) configured -%% a certain setting -is_default(Key, AppEnv, Mappings) -> - {OSVarName, Key, _TransformType} = lists:keyfind(Key, 2, Mappings), - not lists:keymember(Key, 1, AppEnv) andalso os:getenv(OSVarName) =:= false. - %% sampler configuration is unique since it has the _ARG that is a sort of %% sub-configuration of the sampler config, and isn't a list. -spec sampler(list(), t()) -> t(). @@ -502,9 +493,9 @@ transform(span_processors, Unknown) -> ?LOG_WARNING("processors value must be a list, but ~ts given. No span processors will be used", [Unknown]), []; transform(span_processor, batch) -> - {otel_batch_processor, ?BATCH_PROCESSOR_DEFAULTS}; + {otel_batch_processor, #{}}; transform(span_processor, simple) -> - {otel_simple_processor, ?SIMPLE_PROCESSOR_DEFAULTS}; + {otel_simple_processor, #{}}; transform(span_processor, SpanProcessor) -> SpanProcessor; transform(readers, Readers) -> diff --git a/apps/opentelemetry/test/otel_configuration_SUITE.erl b/apps/opentelemetry/test/otel_configuration_SUITE.erl index e021c45b..9387b3a9 100644 --- a/apps/opentelemetry/test/otel_configuration_SUITE.erl +++ b/apps/opentelemetry/test/otel_configuration_SUITE.erl @@ -21,7 +21,9 @@ all() -> log_level, propagators, propagators_b3, propagators_b3multi, otlp_exporter, jaeger_exporter, zipkin_exporter, none_exporter, app_env_exporter, otlp_metrics_exporter, none_metrics_exporter, span_limits, bad_span_limits, - bad_app_config, deny_list, resource_detectors, span_processors]. + bad_app_config, deny_list, resource_detectors, span_processors, + processor_exporter_not_overridden, + processor_config_precedence_with_os_env]. init_per_testcase(empty_os_environment, Config) -> Vars = [], @@ -188,7 +190,13 @@ init_per_testcase(app_env_exporter, Config) -> [{os_vars, []} | Config]; init_per_testcase(span_processors, Config) -> - [{os_vars, []} | Config]. + [{os_vars, []} | Config]; +init_per_testcase(processor_exporter_not_overridden, Config) -> + [{os_vars, []} | Config]; +init_per_testcase(processor_config_precedence_with_os_env, Config) -> + Vars = [{"OTEL_TRACES_EXPORTER", "none"}], + setup_env(Vars), + [{os_vars, Vars} | Config]. end_per_testcase(_, Config) -> Vars = ?config(os_vars, Config), @@ -395,10 +403,11 @@ span_processors(_Config) -> scheduled_delay_ms := 5000}}]}, otel_configuration:merge_with_os([{span_processor, batch}])), + %% processor-level config takes precedence over top-level bsp_ settings ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := {opentelemetry_exporter, #{endpoints := ["https://example.com"]}}, - exporting_timeout_ms := 2, - max_queue_size := 1, + exporting_timeout_ms := 3, + max_queue_size := 4, scheduled_delay_ms := 15000}}]}, otel_configuration:merge_with_os([{processors, [{otel_batch_processor, #{exporter => {opentelemetry_exporter,#{endpoints => ["https://example.com"]}}, scheduled_delay_ms => 15000, @@ -414,6 +423,103 @@ span_processors(_Config) -> ok. +processor_exporter_not_overridden(_Config) -> + %% processor-level exporter should not be overridden by top-level traces_exporter + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := {my_exporter, #{}}}}]}, + otel_configuration:merge_with_os([{traces_exporter, {other_exporter, #{}}}, + {processors, [{otel_batch_processor, + #{exporter => {my_exporter, #{}}}}]}])), + + %% processor-level exporter preserved when no top-level traces_exporter is set + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := {my_exporter, #{}}}}]}, + otel_configuration:merge_with_os([{processors, [{otel_batch_processor, + #{exporter => {my_exporter, #{}}}}]}])), + + %% top-level traces_exporter applies when processor opts don't include exporter + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := {custom_exporter, #{}}}}]}, + otel_configuration:merge_with_os([{traces_exporter, {custom_exporter, #{}}}, + {span_processor, batch}])), + + %% same for simple processor + ?assertMatch(#{processors := [{otel_simple_processor, #{exporter := {my_exporter, #{}}}}]}, + otel_configuration:merge_with_os([{traces_exporter, {other_exporter, #{}}}, + {processors, [{otel_simple_processor, + #{exporter => {my_exporter, #{}}}}]}])), + + %% processor-level 'none' exporter preserved even when top-level sets something else + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := none}}]}, + otel_configuration:merge_with_os([{traces_exporter, {other_exporter, #{}}}, + {processors, [{otel_batch_processor, + #{exporter => none}}]}])), + + %% processor-level timeout preserved when bsp_ also set + ?assertMatch(#{processors := [{otel_batch_processor, #{exporting_timeout_ms := 500}}]}, + otel_configuration:merge_with_os([{bsp_exporting_timeout_ms, 999}, + {processors, [{otel_batch_processor, + #{exporting_timeout_ms => 500}}]}])), + + %% processor-level scheduled_delay_ms preserved when bsp_ also set + ?assertMatch(#{processors := [{otel_batch_processor, #{scheduled_delay_ms := 100}}]}, + otel_configuration:merge_with_os([{bsp_scheduled_delay_ms, 9999}, + {processors, [{otel_batch_processor, + #{scheduled_delay_ms => 100}}]}])), + + %% simple processor timeout preserved when ssp_ also set + ?assertMatch(#{processors := [{otel_simple_processor, #{exporting_timeout_ms := 200}}]}, + otel_configuration:merge_with_os([{ssp_exporting_timeout_ms, 888}, + {processors, [{otel_simple_processor, + #{exporting_timeout_ms => 200}}]}])), + + %% bsp_ settings still apply when processor opts don't include those keys + ?assertMatch(#{processors := [{otel_batch_processor, #{exporting_timeout_ms := 7, + max_queue_size := 11}}]}, + otel_configuration:merge_with_os([{bsp_exporting_timeout_ms, 7}, + {bsp_max_queue_size, 11}, + {processors, [{otel_batch_processor, + #{scheduled_delay_ms => 100}}]}])), + + %% multiple processors - each gets independent config resolution + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := {batch_exp, #{}}}}, + {otel_simple_processor, #{exporter := {simple_exp, #{}}}}]}, + otel_configuration:merge_with_os([{traces_exporter, {global_exp, #{}}}, + {processors, [{otel_batch_processor, + #{exporter => {batch_exp, #{}}}}, + {otel_simple_processor, + #{exporter => {simple_exp, #{}}}}]}])), + + %% default config fully populated when using span_processor shorthand + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := {opentelemetry_exporter, #{}}, + exporting_timeout_ms := 30000, + max_queue_size := 2048, + scheduled_delay_ms := 5000}}]}, + otel_configuration:merge_with_os([{span_processor, batch}])), + + ok. + +processor_config_precedence_with_os_env(_Config) -> + %% OTEL_TRACES_EXPORTER=none is set in init_per_testcase + + %% OS env 'none' applies to batch processor via shorthand + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := none}}]}, + otel_configuration:merge_with_os([{span_processor, batch}])), + + %% OS env 'none' applies to simple processor via shorthand + ?assertMatch(#{processors := [{otel_simple_processor, #{exporter := none}}]}, + otel_configuration:merge_with_os([{span_processor, simple}])), + + %% OS env does NOT override processor-level exporter + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := {my_exporter, #{}}}}]}, + otel_configuration:merge_with_os([{processors, [{otel_batch_processor, + #{exporter => {my_exporter, #{}}}}]}])), + + %% OS env applies when processor opts don't include exporter + ?assertMatch(#{processors := [{otel_batch_processor, #{exporter := none, + scheduled_delay_ms := 100}}]}, + otel_configuration:merge_with_os([{processors, [{otel_batch_processor, + #{scheduled_delay_ms => 100}}]}])), + + ok. + %% setup_env(Vars) ->