From 4deca7769ef37fa99bf7a0cc0dd877aa0edad1db Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:36:16 -0700 Subject: [PATCH 1/3] feat: Migrate from error_logger to OTP logger module Replace all error_logger calls (info_msg, warning_msg, error_msg) with their logger equivalents (logger:info, logger:warning, logger:error) across 18 source files and 3 test files. All logger calls include #{domain => [ldclient]} metadata, enabling users to filter SDK logs using standard logger configuration. The logger module also automatically attaches module, function, and line metadata. This addresses GitHub issue #160. The error_logger module is marked for eventual removal from OTP, and logger (available since OTP 21) is the recommended replacement. All supported OTP versions (24+) include logger. Migration notes for users: - Users with custom error_logger report handlers that specifically capture SDK logs should switch to logger handlers instead. Example: logger:add_handler(my_handler, my_module, #{ filter_default => stop, filters => [{ldclient_only, {fun logger_filters:domain/2, {log, sub, [ldclient]}}}] }) - Elixir users benefit automatically: SDK logs now appear as native Elixir Logger events with proper metadata instead of wrapped error_logger reports. - No public API changes. This is an internal logging infrastructure change only. --- src/ldclient.erl | 2 +- src/ldclient_config.erl | 8 ++-- src/ldclient_eval.erl | 24 +++++------ src/ldclient_event_process_server.erl | 4 +- src/ldclient_event_server.erl | 6 +-- src/ldclient_event_sup.erl | 2 +- src/ldclient_instance_sup.erl | 2 +- src/ldclient_rollout.erl | 2 +- src/ldclient_storage_cache_server.erl | 2 +- src/ldclient_storage_ets_server.erl | 2 +- src/ldclient_storage_map_server.erl | 2 +- src/ldclient_storage_redis_server.erl | 22 +++++----- src/ldclient_testdata.erl | 2 +- src/ldclient_update_file_server.erl | 10 ++--- src/ldclient_update_null_server.erl | 4 +- src/ldclient_update_poll_server.erl | 14 +++---- src/ldclient_update_stream_server.erl | 28 ++++++------- src/ldclient_update_testdata_server.erl | 4 +- test-tls/ldclient_tls_options_SUITE.erl | 4 +- test/ldclient_clause_SUITE.erl | 6 +-- test/ldclient_sdk_key_logging_SUITE.erl | 53 ++++++++----------------- 21 files changed, 91 insertions(+), 112 deletions(-) diff --git a/src/ldclient.erl b/src/ldclient.erl index 1767536..47da641 100644 --- a/src/ldclient.erl +++ b/src/ldclient.erl @@ -283,7 +283,7 @@ ensure_context(#{kind := _Kind} = ContextOrContext) -> ContextOrContext; ensure_context(UserOrContext) -> case maps:is_key(key, UserOrContext) of %% We allow legacy users to have an empty key, but we log it. - false -> error_logger:warning_msg("Context key is blank. Flag evaluation will proceed, but the context will not be stored in Launchdarkly"); + false -> logger:warning("Context key is blank. Flag evaluation will proceed, but the context will not be stored in Launchdarkly", #{domain => [ldclient]}); true -> ok end, ldclient_context:new_from_user(UserOrContext). diff --git a/src/ldclient_config.erl b/src/ldclient_config.erl index 4a13de4..2298b49 100644 --- a/src/ldclient_config.erl +++ b/src/ldclient_config.erl @@ -388,8 +388,8 @@ set_valid_tag(Key, Value, MapOrUndefined) -> false -> #{Key => Value} end; false -> - error_logger:warning_msg("The application ~p was invalid. Must only contain letters, numbers, ., _ or -," - " be 64 characters or less, and cannot be an empty string.", [Key]), + logger:warning("The application ~p was invalid. Must only contain letters, numbers, ., _ or -," + " be 64 characters or less, and cannot be an empty string.", [Key], #{domain => [ldclient]}), MapOrUndefined end. @@ -445,10 +445,10 @@ tls_base_options() -> tls_basic_options(true) -> tls_basic_linux_options(); tls_basic_options(false) -> - error_logger:warning_msg("TLS options are falling back to using the certifi store. + logger:warning("TLS options are falling back to using the certifi store. This means the OS certificate store was not in the default location (/etc/ssl/certs/ca-certificates.crt). Please specify a custom location. You can use tls_ca_certfile_options, or fully specify the tls_options. - You may see this warning in development on Mac/Windows."), + You may see this warning in development on Mac/Windows.", #{domain => [ldclient]}), tls_basic_certifi_options(). -spec validate_tag_value(Value :: binary() | undefined) -> boolean(). diff --git a/src/ldclient_eval.erl b/src/ldclient_eval.erl index 05d8ac0..9ea9e37 100644 --- a/src/ldclient_eval.erl +++ b/src/ldclient_eval.erl @@ -86,7 +86,7 @@ flag_key_for_context(_Tag, _FlagKey, _Context, DefaultValue, offline, _) -> flag_key_for_context(_Tag, _FlagKey, _Context, DefaultValue, _, not_initialized) -> {{null, DefaultValue, {error, client_not_ready}}, []}; flag_key_for_context(Tag, FlagKey, Context, DefaultValue, online, store_initialized) -> - error_logger:warning_msg("Variation called before LaunchDarkly client initialization completed - using last known values from feature store."), + logger:warning("Variation called before LaunchDarkly client initialization completed - using last known values from feature store.", #{domain => [ldclient]}), FeatureStore = ldclient_config:get_value(Tag, feature_store), FlagRecs = FeatureStore:get(Tag, features, FlagKey), flag_recs_for_context(FlagKey, FlagRecs, Context, FeatureStore, Tag, DefaultValue); @@ -145,7 +145,7 @@ all_flags_state(_Context, _Options, _Tag, offline, _) -> all_flags_state(_Context, _Options, _Tag, _, not_initialized) -> #{<<"$valid">> => false, <<"$flagsState">> => #{}}; all_flags_state(Context, #{with_reasons := WithReason} = _Options, Tag, Offline, store_initialized) -> - error_logger:warning_msg("Called allFlagsState before client initialization; using last known values from data store."), + logger:warning("Called allFlagsState before client initialization; using last known values from data store.", #{domain => [ldclient]}), all_flags_state(Context, #{with_reasons := WithReason} = _Options, Tag, Offline, initialized); all_flags_state(Context, #{with_reasons := WithReason} = Options, Tag, _, initialized) -> FeatureStore = ldclient_config:get_value(Tag, feature_store), @@ -246,7 +246,7 @@ all_flags_eval(_Context, _Tag, offline, _) -> all_flags_eval(_Context, _Tag, _, not_initialized) -> #{flag_values => #{}}; all_flags_eval(Context, Tag, Offline, store_initialized) -> - error_logger:warning_msg("Called allFlagsState before client initialization; using last known values from data store."), + logger:warning("Called allFlagsState before client initialization; using last known values from data store.", #{domain => [ldclient]}), all_flags_eval(Context, Tag, Offline, initialized); all_flags_eval(Context, Tag, online, initialized) -> FeatureStore = ldclient_config:get_value(Tag, feature_store), @@ -309,13 +309,13 @@ get_initialization_state(Tag, false) -> ) -> result(). flag_recs_for_context(FlagKey, [], Context, _FeatureStore, _Tag, DefaultValue) -> % Flag not found - error_logger:warning_msg("Unknown feature flag ~p; returning default value", [FlagKey]), + logger:warning("Unknown feature flag ~p; returning default value", [FlagKey], #{domain => [ldclient]}), Reason = {error, flag_not_found}, Events = [ldclient_event:new_for_unknown_flag(FlagKey, Context, DefaultValue, Reason)], {{null, DefaultValue, Reason}, Events}; flag_recs_for_context(FlagKey, [{FlagKey, #{deleted := true}} | _], Context, _FeatureStore, _Tag, DefaultValue) -> % Flag found, but it's deleted - error_logger:warning_msg("Unknown feature flag ~p; returning default value", [FlagKey]), + logger:warning("Unknown feature flag ~p; returning default value", [FlagKey], #{domain => [ldclient]}), Reason = {error, flag_not_found}, Events = [ldclient_event:new_for_unknown_flag(FlagKey, Context, DefaultValue, Reason)], {{null, DefaultValue, Reason}, Events}; @@ -394,8 +394,8 @@ check_prerequisites( ) -> case lists:member(PrerequisiteKey, VisitedFlags) of true -> - error_logger:error_msg("Prerequisite of ~p causing a circular reference." - " This is probably a temporary condition due to an incomplete update.", [FlagKey]), + logger:error("Prerequisite of ~p causing a circular reference." + " This is probably a temporary condition due to an incomplete update.", [FlagKey], #{domain => [ldclient]}), flag_for_context_prerequisites({malformed_flag, {error, malformed_flag}}, Flag, Context, FeatureStore, Tag, DefaultValue, Events); false -> @@ -420,7 +420,7 @@ check_prerequisites( check_prerequisite_recs([], PrerequisiteKey, _Variation, _Prerequisites, #{key := FlagKey} = Flag, Context, FeatureStore, Tag, DefaultValue, Events, _VisitedFlags) -> % Short circuit if prerequisite flag is not found - error_logger:error_msg("Could not retrieve prerequisite flag ~p when evaluating ~p", [PrerequisiteKey, FlagKey]), + logger:error("Could not retrieve prerequisite flag ~p when evaluating ~p", [PrerequisiteKey, FlagKey], #{domain => [ldclient]}), flag_for_context_prerequisites({fail, {prerequisite_failed, [PrerequisiteKey]}}, Flag, Context, FeatureStore, Tag, DefaultValue, Events); check_prerequisite_recs([{PrerequisiteKey, PrerequisiteFlag} | _], PrerequisiteKey, Variation, Prerequisites, Flag, @@ -526,7 +526,7 @@ check_rules([Rule | Rest], Flag, Context, FeatureStore, Tag, DefaultValue, Event check_rule_result({Result, Rule, Index}, Rest, Flag, Context, FeatureStore, Tag, DefaultValue, Events). check_rule_result({malformed_flag, _Rule, _Index}, _Rest, #{key := FlagKey} = _Flag, _Context, _FeatureStore, _Tag, DefaultValue, _Events) -> - error_logger:warning_msg("Data inconsistency in feature flag ~p: clause was malformed", [FlagKey]), + logger:warning("Data inconsistency in feature flag ~p: clause was malformed", [FlagKey], #{domain => [ldclient]}), Reason = {error, malformed_flag}, {{null, DefaultValue, Reason}, []}; check_rule_result({no_match, _Rule, Index}, Rest, Flag, Context, FeatureStore, Tag, DefaultValue, Events) -> @@ -546,19 +546,19 @@ flag_for_context_variation_or_rollout(Variation, Reason, Flag, _Context, Default flag_for_context_variation_or_rollout(Rollout, Reason, #{key := FlagKey} = Flag, Context, DefaultValue, Events) when is_map(Rollout) -> case ldclient_rollout:rollout_context(Rollout, Flag, Context) of malformed_flag -> - error_logger:warning_msg("Data inconsistency in feature flag ~p: rollout was malformed", [FlagKey]), + logger:warning("Data inconsistency in feature flag ~p: rollout was malformed", [FlagKey], #{domain => [ldclient]}), {{null, DefaultValue, {error, malformed_flag}}, []}; {Result, InExperiment} -> UpdatedReason = experimentize_reason(InExperiment, Reason), flag_for_context_rollout_result(Result, UpdatedReason, Flag, DefaultValue, Events) end; flag_for_context_variation_or_rollout(null, _Reason, #{key := FlagKey}, _Context, DefaultValue, Events) -> - error_logger:warning_msg("Data inconsistency in feature flag ~p: rule object with no variation or rollout", [FlagKey]), + logger:warning("Data inconsistency in feature flag ~p: rule object with no variation or rollout", [FlagKey], #{domain => [ldclient]}), Reason = {error, malformed_flag}, {{null, DefaultValue, Reason}, Events}. flag_for_context_rollout_result(null, _Reason, #{key := FlagKey}, DefaultValue, Events) -> - error_logger:warning_msg("Data inconsistency in feature flag ~p: variation/rollout object with no variation or rollout", [FlagKey]), + logger:warning("Data inconsistency in feature flag ~p: variation/rollout object with no variation or rollout", [FlagKey], #{domain => [ldclient]}), Reason = {error, malformed_flag}, {{null, DefaultValue, Reason}, Events}; diff --git a/src/ldclient_event_process_server.erl b/src/ldclient_event_process_server.erl index bff2813..0ac76c4 100644 --- a/src/ldclient_event_process_server.erl +++ b/src/ldclient_event_process_server.erl @@ -70,7 +70,7 @@ get_last_server_time(Tag) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(Tag) -> ServerName = get_local_reg_name(Tag), - error_logger:info_msg("Starting event processor for ~p with name ~p", [Tag, ServerName]), + logger:info("Starting event processor for ~p with name ~p", [Tag, ServerName], #{domain => [ldclient]}), gen_server:start_link({local, ServerName}, ?MODULE, [Tag], []). -spec init(Args :: term()) -> @@ -126,7 +126,7 @@ handle_cast({send_events, Events, SummaryEvent}, erlang:send_after(1000, self(), {send, OutputEvents, PayloadId}), State; {error, permanent, Reason} -> - error_logger:error_msg("Permanent error sending events ~p", [Reason]), + logger:error("Permanent error sending events ~p", [Reason], #{domain => [ldclient]}), State end, {noreply, NewState}; diff --git a/src/ldclient_event_server.erl b/src/ldclient_event_server.erl index 6253a9a..0c47091 100644 --- a/src/ldclient_event_server.erl +++ b/src/ldclient_event_server.erl @@ -94,7 +94,7 @@ flush(Tag) when is_atom(Tag) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(Tag) -> ServerName = get_local_reg_name(Tag), - error_logger:info_msg("Starting event storage server for ~p with name ~p", [Tag, ServerName]), + logger:info("Starting event storage server for ~p with name ~p", [Tag, ServerName], #{domain => [ldclient]}), gen_server:start_link({local, ServerName}, ?MODULE, [Tag], []). -spec init(Args :: term()) -> @@ -152,7 +152,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{timer_ref := TimerRef} = _State) -> - error_logger:info_msg("Terminating event service, reason: ~p", [Reason]), + logger:info("Terminating event service, reason: ~p", [Reason], #{domain => [ldclient]}), _ = erlang:cancel_timer(TimerRef), ok; terminate(_Reason, _State) -> @@ -195,7 +195,7 @@ add_event(Tag, #{type := custom, context := Context, timestamp := Timestamp} = E add_raw_event(Event, Events, Capacity) when length(Events) < Capacity -> [Event|Events]; add_raw_event(_, Events, _) -> - error_logger:warning_msg("Exceeded event queue capacity. Increase capacity to avoid dropping events."), + logger:warning("Exceeded event queue capacity. Increase capacity to avoid dropping events.", #{domain => [ldclient]}), Events. -spec add_feature_request_event(ldclient_event:event(), summary_event()) -> diff --git a/src/ldclient_event_sup.erl b/src/ldclient_event_sup.erl index 642b48b..a154d5b 100644 --- a/src/ldclient_event_sup.erl +++ b/src/ldclient_event_sup.erl @@ -21,7 +21,7 @@ -spec start_link(SupName :: atom(), Tag :: atom()) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(SupName, Tag) -> - error_logger:info_msg("Starting event supervisor for ~p with name ~p", [Tag, SupName]), + logger:info("Starting event supervisor for ~p with name ~p", [Tag, SupName], #{domain => [ldclient]}), supervisor:start_link({local, SupName}, ?MODULE, [Tag]). -spec init(Args :: term()) -> diff --git a/src/ldclient_instance_sup.erl b/src/ldclient_instance_sup.erl index 25b1be4..f244d6a 100644 --- a/src/ldclient_instance_sup.erl +++ b/src/ldclient_instance_sup.erl @@ -37,7 +37,7 @@ child_spec(Id, Args) -> ) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(SupName, UpdateSupName, UpdateWorkerModule, EventSupName, Tag) -> - error_logger:info_msg("Starting instance supervisor for ~p with name ~p", [Tag, SupName]), + logger:info("Starting instance supervisor for ~p with name ~p", [Tag, SupName], #{domain => [ldclient]}), supervisor:start_link({local, SupName}, ?MODULE, [UpdateSupName, UpdateWorkerModule, EventSupName, Tag]). -spec init(Args :: term()) -> diff --git a/src/ldclient_rollout.erl b/src/ldclient_rollout.erl index 135cc31..8f44c3b 100644 --- a/src/ldclient_rollout.erl +++ b/src/ldclient_rollout.erl @@ -63,7 +63,7 @@ parse_rollout_kind(<<"experiment">>) -> experiment; parse_rollout_kind(<<"rollout">>) -> rollout; parse_rollout_kind(Kind) -> %% If we are not familiar with this kind, then log it and default to rollout. - error_logger:warning_msg("Unrecognized rollout type: ~p", [Kind]), + logger:warning("Unrecognized rollout type: ~p", [Kind], #{domain => [ldclient]}), rollout. -spec rollout_context( diff --git a/src/ldclient_storage_cache_server.erl b/src/ldclient_storage_cache_server.erl index 08405f2..6d73129 100644 --- a/src/ldclient_storage_cache_server.erl +++ b/src/ldclient_storage_cache_server.erl @@ -35,7 +35,7 @@ %%=================================================================== start_link(WorkerRegName, Tag) -> - error_logger:info_msg("Starting map server with name ~p", [WorkerRegName]), + logger:info("Starting map server with name ~p", [WorkerRegName], #{domain => [ldclient]}), gen_server:start_link({local, WorkerRegName}, ?MODULE, [Tag], []). init([Tag]) -> diff --git a/src/ldclient_storage_ets_server.erl b/src/ldclient_storage_ets_server.erl index 5ae22eb..8a59222 100644 --- a/src/ldclient_storage_ets_server.erl +++ b/src/ldclient_storage_ets_server.erl @@ -35,7 +35,7 @@ %%=================================================================== start_link(WorkerRegName, Tag) -> - error_logger:info_msg("Starting ets server with name ~p", [WorkerRegName]), + logger:info("Starting ets server with name ~p", [WorkerRegName], #{domain => [ldclient]}), gen_server:start_link({local, WorkerRegName}, ?MODULE, [Tag], []). init([Tag]) -> diff --git a/src/ldclient_storage_map_server.erl b/src/ldclient_storage_map_server.erl index 5ae7c10..e5d9d59 100644 --- a/src/ldclient_storage_map_server.erl +++ b/src/ldclient_storage_map_server.erl @@ -36,7 +36,7 @@ %%=================================================================== start_link(WorkerRegName, Tag) -> - error_logger:info_msg("Starting map server with name ~p", [WorkerRegName]), + logger:info("Starting map server with name ~p", [WorkerRegName], #{domain => [ldclient]}), gen_server:start_link({local, WorkerRegName}, ?MODULE, [Tag], []). init([Tag]) -> diff --git a/src/ldclient_storage_redis_server.erl b/src/ldclient_storage_redis_server.erl index 68b3479..7cd6943 100644 --- a/src/ldclient_storage_redis_server.erl +++ b/src/ldclient_storage_redis_server.erl @@ -45,7 +45,7 @@ tag => atom() %%=================================================================== start_link(WorkerRegName, Tag) -> - error_logger:info_msg("Starting redis server with name ~p", [WorkerRegName]), + logger:info("Starting redis server with name ~p", [WorkerRegName], #{domain => [ldclient]}), gen_server:start_link({local, WorkerRegName}, ?MODULE, [Tag], []). -spec set_tls_options(Options :: list(), TlsOptions :: list()) -> OutOptions :: list(). @@ -211,7 +211,7 @@ create_bucket(false, Bucket, Client, Prefix, Buckets) -> {ok, _} -> {ok, [Bucket | Buckets]}; {error, Reason} -> - error_logger:error_msg("Redis connection error during create_bucket for ~p: ~p", [Bucket, Reason]), + logger:error("Redis connection error during create_bucket for ~p: ~p", [Bucket, Reason], #{domain => [ldclient]}), {ok, [Bucket | Buckets]} end. @@ -231,7 +231,7 @@ empty_bucket(true, Bucket, Client, Prefix) -> {ok, _} = create_bucket(false, Bucket, Client, Prefix, []), ok; {error, Reason} -> - error_logger:error_msg("Redis connection error during empty_bucket for ~p: ~p", [Bucket, Reason]), + logger:error("Redis connection error during empty_bucket for ~p: ~p", [Bucket, Reason], #{domain => [ldclient]}), ok end. @@ -253,7 +253,7 @@ all_items(true, Bucket, Client, Prefix) -> not lists:member(Elem, NullFilter) end, Values), %This removes the initial null key and value pairs(NewValues, Bucket); {error, Reason} -> - error_logger:error_msg("Redis error listing all items in bucket ~p: ~s", [Bucket, format_error(Reason)]), + logger:error("Redis error listing all items in bucket ~p: ~s", [Bucket, format_error(Reason)], #{domain => [ldclient]}), [] end. @@ -296,7 +296,7 @@ lookup_key(true, Key, Bucket, Client, Prefix) -> end end; {error, Reason} -> - error_logger:error_msg("Redis error looking up key ~p in bucket ~p: ~s", [Key, Bucket, format_error(Reason)]), + logger:error("Redis error looking up key ~p in bucket ~p: ~s", [Key, Bucket, format_error(Reason)], #{domain => [ldclient]}), [] end. @@ -317,18 +317,18 @@ upsert_items(true, Items, Bucket, Client, Prefix) -> case eredis:q(Client, ["HSET", bucket_name(Prefix, Bucket), K, jsx:encode(V)]) of {ok, _} -> ok; {error, Reason} -> - error_logger:error_msg("Redis connection error during HSET for ~p key ~p: ~p", [Bucket, K, Reason]), + logger:error("Redis connection error during HSET for ~p key ~p: ~p", [Bucket, K, Reason], #{domain => [ldclient]}), ok end end, ok, Items), case eredis:q(Client, ["UNWATCH"]) of {ok, <<"OK">>} -> Result; {error, Reason} -> - error_logger:error_msg("Redis connection error during UNWATCH for ~p: ~p", [Bucket, Reason]), + logger:error("Redis connection error during UNWATCH for ~p: ~p", [Bucket, Reason], #{domain => [ldclient]}), ok end; {error, Reason} -> - error_logger:error_msg("Redis connection error during WATCH for ~p: ~p", [Bucket, Reason]), + logger:error("Redis connection error during WATCH for ~p: ~p", [Bucket, Reason], #{domain => [ldclient]}), ok end. @@ -360,7 +360,7 @@ delete_key(true, Key, Bucket, Client, Prefix) -> case eredis:q(Client, ["HDEL", bucket_name(Prefix, Bucket), Key]) of {ok, _} -> ok; {error, Reason} -> - error_logger:error_msg("Redis connection error during delete_key for ~p key ~p: ~p", [Bucket, Key, Reason]), + logger:error("Redis connection error during delete_key for ~p key ~p: ~p", [Bucket, Key, Reason], #{domain => [ldclient]}), ok end. @@ -380,7 +380,7 @@ set_init(Client, Prefix) -> case eredis:q(Client, ["SET", lists:concat([Prefix, ":$inited"]), ""]) of {ok, _} -> ok; {error, Reason} -> - error_logger:error_msg("Redis connection error during set_init: ~p", [Reason]), + logger:error("Redis connection error during set_init: ~p", [Reason], #{domain => [ldclient]}), ok end. @@ -393,6 +393,6 @@ get_init(Client, Prefix) -> _ -> true end; {error, Reason} -> - error_logger:error_msg("Redis error getting init flag: ~s", [format_error(Reason)]), + logger:error("Redis error getting init flag: ~s", [format_error(Reason)], #{domain => [ldclient]}), false end. diff --git a/src/ldclient_testdata.erl b/src/ldclient_testdata.erl index f75271f..ea8c7d8 100644 --- a/src/ldclient_testdata.erl +++ b/src/ldclient_testdata.erl @@ -272,7 +272,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, _State) -> - error_logger:info_msg("Terminating, reason: ~p; Pid none~n", [Reason]), + logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), ok. %% @doc diff --git a/src/ldclient_update_file_server.erl b/src/ldclient_update_file_server.erl index 97bc110..79415f4 100644 --- a/src/ldclient_update_file_server.erl +++ b/src/ldclient_update_file_server.erl @@ -38,7 +38,7 @@ -spec start_link(Tag :: atom()) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(Tag) -> - error_logger:info_msg("Starting file update server for ~p", [Tag]), + logger:info("Starting file update server for ~p", [Tag], #{domain => [ldclient]}), gen_server:start_link(?MODULE, [Tag], []). -spec init(Args :: term()) -> @@ -94,10 +94,10 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{timer_ref := undefined} = _State) -> - error_logger:info_msg("Terminating, reason: ~p; Pid none~n", [Reason]), + logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), ok; terminate(Reason, #{timer_ref := TimerRef} = _State) -> - error_logger:info_msg("Terminating, reason: ~p; Pid none~n", [Reason]), + logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), _ = timer:cancel(TimerRef), ok. @@ -164,7 +164,7 @@ read_file(FilePath, ".json") -> {ok, Data} = file:read_file(FilePath), {ok, jsx:decode(Data, [return_maps])}; read_file(FilePath, _Extension) -> - error_logger:warning_msg("File had unrecognized file extension. Valid extensions are .yaml and .json. File: ~p", [FilePath]), + logger:warning("File had unrecognized file extension. Valid extensions are .yaml and .json. File: ~p", [FilePath], #{domain => [ldclient]}), {error, #{}}. -spec try_read_file(FilePath :: string() | binary(), Extension :: string()) -> {ok | error, map()}. @@ -172,7 +172,7 @@ try_read_file(FilePath, Extension) -> try read_file(FilePath, Extension) catch _:Exception:Stacktrace -> - error_logger:warning_msg("Problem reading file: ~p Exception: ~p ~p", [FilePath, Exception, Stacktrace]), + logger:warning("Problem reading file: ~p Exception: ~p ~p", [FilePath, Exception, Stacktrace], #{domain => [ldclient]}), {error, #{}} end. diff --git a/src/ldclient_update_null_server.erl b/src/ldclient_update_null_server.erl index 44304d4..7f4f4a7 100644 --- a/src/ldclient_update_null_server.erl +++ b/src/ldclient_update_null_server.erl @@ -30,7 +30,7 @@ -spec start_link(Tag :: atom()) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(Tag) -> - error_logger:info_msg("Starting null update server for ~p", [Tag]), + logger:info("Starting null update server for ~p", [Tag], #{domain => [ldclient]}), gen_server:start_link(?MODULE, [Tag], []). -spec init(Args :: term()) -> @@ -64,7 +64,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, _State) -> - error_logger:info_msg("Terminating, reason: ~p; Pid none~n", [Reason]), + logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), ok. code_change(_OldVsn, State, _Extra) -> diff --git a/src/ldclient_update_poll_server.erl b/src/ldclient_update_poll_server.erl index 89ffaa5..59faf45 100644 --- a/src/ldclient_update_poll_server.erl +++ b/src/ldclient_update_poll_server.erl @@ -42,7 +42,7 @@ -spec start_link(Tag :: atom()) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(Tag) -> - error_logger:info_msg("Starting polling update server for ~p", [Tag]), + logger:info("Starting polling update server for ~p", [Tag], #{domain => [ldclient]}), gen_server:start_link(?MODULE, [Tag], []). -spec init(Args :: term()) -> @@ -98,7 +98,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{timer_ref := TimerRef} = _State) -> - error_logger:info_msg("Terminating polling, reason: ~p", [Reason]), + logger:info("Terminating polling, reason: ~p", [Reason], #{domain => [ldclient]}), _ = timer:cancel(TimerRef), ok; terminate(_Reason, _State) -> @@ -133,16 +133,16 @@ poll(#{ feature_store := FeatureStore, -spec process_response(ldclient_update_requestor:response(), atom(), atom(), string()) -> ok. process_response({error, {bad_status, 401, _Reason}}, _, _, Uri) -> - error_logger:warning_msg("Invalid SDK key when when polling for updates at URL ~p. Verify that your SDK key is correct.", [Uri]), + logger:warning("Invalid SDK key when when polling for updates at URL ~p. Verify that your SDK key is correct.", [Uri], #{domain => [ldclient]}), ok; process_response({error, {bad_status, 404, _Reason}}, _, _, Uri) -> - error_logger:warning_msg("Resource not found when polling for updates at URL ~p.", [Uri]), + logger:warning("Resource not found when polling for updates at URL ~p.", [Uri], #{domain => [ldclient]}), ok; process_response({error, {bad_status, StatusCode, Reason}}, _, _, Uri) when StatusCode >= 300 -> - error_logger:warning_msg("Unexpected response code: ~p when polling for updates at URL ~p: ~p.", [StatusCode, Uri, Reason]), + logger:warning("Unexpected response code: ~p when polling for updates at URL ~p: ~p.", [StatusCode, Uri, Reason], #{domain => [ldclient]}), ok; process_response({error, network_error}, _, _, Uri) -> - error_logger:warning_msg("Failed to connect to update server at: %p", [Uri]), + logger:warning("Failed to connect to update server at: %p", [Uri], #{domain => [ldclient]}), ok; process_response({ok, not_modified}, _, _, _) -> ok; process_response({ok, ResponseBody}, FeatureStore, Tag, _) -> @@ -157,7 +157,7 @@ process_response({ok, ResponseBody}, FeatureStore, Tag, _) -> process_response_body(ResponseBody, ldclient_storage_redis, Tag) -> Data = jsx:decode(ResponseBody, [return_maps]), [Flags, Segments] = get_put_items(Data), - error_logger:info_msg("Received poll event with ~p flags and ~p segments", [maps:size(Flags), maps:size(Segments)]), + logger:info("Received poll event with ~p flags and ~p segments", [maps:size(Flags), maps:size(Segments)], #{domain => [ldclient]}), ok = ldclient_storage_redis:upsert_clean(Tag, features, Flags), ok = ldclient_storage_redis:upsert_clean(Tag, segments, Segments), ok = ldclient_storage_redis:set_init(Tag); diff --git a/src/ldclient_update_stream_server.erl b/src/ldclient_update_stream_server.erl index 5902eb4..8bc061e 100644 --- a/src/ldclient_update_stream_server.erl +++ b/src/ldclient_update_stream_server.erl @@ -42,7 +42,7 @@ -spec start_link(Tag :: atom()) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(Tag) -> - error_logger:info_msg("Starting streaming update server for ~p", [Tag]), + logger:info("Starting streaming update server for ~p", [Tag], #{domain => [ldclient]}), gen_server:start_link(?MODULE, [Tag], []). -spec init(Args :: term()) -> @@ -86,7 +86,7 @@ handle_cast(_Request, State) -> {noreply, State}. handle_info({listen}, #{stream_uri := Uri} = State) -> - error_logger:info_msg("Starting streaming connection to URL: ~p", [Uri]), + logger:info("Starting streaming connection to URL: ~p", [Uri], #{domain => [ldclient]}), NewState = do_listen(State), {noreply, NewState}; handle_info({'DOWN', _Mref, process, ShotgunPid, Reason}, #{conn := ShotgunPid, backoff := Backoff} = State) -> @@ -94,10 +94,10 @@ handle_info({'DOWN', _Mref, process, ShotgunPid, Reason}, #{conn := ShotgunPid, _ = ldclient_backoff:fire(NewBackoff), % Reason from DOWN message could contain connection details with headers/SDK keys SafeReason = ldclient_key_redaction:format_shotgun_error(Reason), - error_logger:warning_msg("Got DOWN message from shotgun pid with reason: ~s, will retry in ~p ms~n", [SafeReason, maps:get(current, NewBackoff)]), + logger:warning("Got DOWN message from shotgun pid with reason: ~s, will retry in ~p ms~n", [SafeReason, maps:get(current, NewBackoff)], #{domain => [ldclient]}), {noreply, State#{conn := undefined, backoff := NewBackoff}}; handle_info({timeout, _TimerRef, listen}, State) -> - error_logger:info_msg("Reconnecting streaming connection...~n"), + logger:info("Reconnecting streaming connection...~n", #{domain => [ldclient]}), NewState = do_listen(State), {noreply, NewState}; handle_info(_Info, State) -> @@ -106,10 +106,10 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{conn := undefined} = _State) -> - error_logger:info_msg("Terminating, reason: ~p; Pid none~n", [Reason]), + logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), ok; terminate(Reason, #{conn := ShotgunPid} = _State) -> - error_logger:info_msg("Terminating streaming connection, reason: ~p; Pid ~p~n", [Reason, ShotgunPid]), + logger:info("Terminating streaming connection, reason: ~p; Pid ~p~n", [Reason, ShotgunPid], #{domain => [ldclient]}), ok = shotgun:close(ShotgunPid). code_change(_OldVsn, State, _Extra) -> @@ -136,7 +136,7 @@ do_listen(#{ {error, permanent, Reason} -> % Reason here is already safe: either a sanitized string from format_shotgun_error % or an integer status code from the do_listen/5 method. - error_logger:error_msg("Stream encountered permanent error ~p, giving up~n", [Reason]), + logger:error("Stream encountered permanent error ~p, giving up~n", [Reason], #{domain => [ldclient]}), State; {ok, Pid} -> NewBackoff = ldclient_backoff:succeed(Backoff), @@ -154,7 +154,7 @@ do_listen(#{ -spec do_listen_fail_backoff(ldclient_backoff:backoff(), atom(), term()) -> ldclient_backoff:backoff(). do_listen_fail_backoff(Backoff, Code, Reason) -> NewBackoff = ldclient_backoff:fail(Backoff), - error_logger:warning_msg("Error establishing streaming connection (~p): ~p, will retry in ~p ms", [Code, Reason, maps:get(current, NewBackoff)]), + logger:warning("Error establishing streaming connection (~p): ~p, will retry in ~p ms", [Code, Reason, maps:get(current, NewBackoff)], #{domain => [ldclient]}), _ = ldclient_backoff:fire(NewBackoff), NewBackoff. @@ -179,12 +179,12 @@ do_listen(Uri, FeatureStore, Tag, GunOpts, Headers) -> catch Code:_Reason -> % Exception when processing event - don't log exception details % as they could theoretically contain sensitive data - error_logger:warning_msg("Invalid SSE event error (~p)", [Code]), + logger:warning("Invalid SSE event error (~p)", [Code], #{domain => [ldclient]}), shotgun:close(Pid) end; (fin, _Ref, _Bin) -> % Connection ended, close monitored shotgun client pid, so we can reconnect - error_logger:warning_msg("Streaming connection ended"), + logger:warning("Streaming connection ended", #{domain => [ldclient]}), shotgun:close(Pid) end, Options = #{async => true, async_mode => sse, handle_event => F, allow_reconnect => false}, @@ -234,13 +234,13 @@ decode_data(_, Data) -> jsx:decode(Data, [return_maps]). -spec process_items(EventOperation :: ldclient_storage_engine:event_operation(), Data :: map(), FeatureStore :: atom(), Tag :: atom()) -> ok. process_items(put, Data, ldclient_storage_redis, Tag) -> [Flags, Segments] = get_put_items(Data), - error_logger:info_msg("Received stream event with ~p flags and ~p segments", [maps:size(Flags), maps:size(Segments)]), + logger:info("Received stream event with ~p flags and ~p segments", [maps:size(Flags), maps:size(Segments)], #{domain => [ldclient]}), ok = ldclient_storage_redis:upsert_clean(Tag, features, Flags), ok = ldclient_storage_redis:upsert_clean(Tag, segments, Segments), ok = ldclient_storage_redis:set_init(Tag); process_items(put, Data, FeatureStore, Tag) -> [Flags, Segments] = get_put_items(Data), - error_logger:info_msg("Received event with ~p flags and ~p segments", [maps:size(Flags), maps:size(Segments)]), + logger:info("Received event with ~p flags and ~p segments", [maps:size(Flags), maps:size(Segments)], #{domain => [ldclient]}), ParsedFlags = maps:map( fun(_K, V) -> ldclient_flag:new(V) end , Flags), @@ -255,7 +255,7 @@ process_items(patch, Data, FeatureStore, Tag) -> ok = maybe_patch_item(FeatureStore, Tag, Bucket, Key, Item, ParseFunction); error -> #{<<"path">> := Path} = Data, - error_logger:warning_msg("Unrecognized patch path ~p", [Path]), + logger:warning("Unrecognized patch path ~p", [Path], #{domain => [ldclient]}), ok end; process_items(delete, Data, FeatureStore, Tag) -> @@ -281,7 +281,7 @@ delete_items(#{<<"path">> := <<"/flags/",Key/binary>>, <<"version">> := Version} delete_items(#{<<"path">> := <<"/segments/",Key/binary>>, <<"version">> := Version}, FeatureStore, Tag) -> ok = maybe_delete_item(FeatureStore, Tag, segments, Key, Version); delete_items(_Path, _FeatureStore, _Tag) -> - error_logger:error_msg("Invalid delete path"). + logger:error("Invalid delete path", #{domain => [ldclient]}). -spec maybe_patch_item(atom(), atom(), atom(), binary(), map(), fun()) -> ok. maybe_patch_item(ldclient_storage_redis, Tag, Bucket, Key, Item, _ParseFunction) -> diff --git a/src/ldclient_update_testdata_server.erl b/src/ldclient_update_testdata_server.erl index fc8c18a..de8e2c7 100644 --- a/src/ldclient_update_testdata_server.erl +++ b/src/ldclient_update_testdata_server.erl @@ -33,7 +33,7 @@ -spec start_link(Tag :: atom()) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link(Tag) -> - error_logger:info_msg("Starting testdata update server for ~p", [Tag]), + logger:info("Starting testdata update server for ~p", [Tag], #{domain => [ldclient]}), gen_server:start_link(?MODULE, [Tag], []). -spec init(Args :: term()) -> @@ -74,7 +74,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{ tag := Tag, test_data_server := TestDataServer }) -> - error_logger:info_msg("Terminating, reason: ~p; Pid: ~p ~n", [Reason, self()]), + logger:info("Terminating, reason: ~p; Pid: ~p ~n", [Reason, self()], #{domain => [ldclient]}), gen_server:call(TestDataServer, {unregister_instance, Tag}), ok. diff --git a/test-tls/ldclient_tls_options_SUITE.erl b/test-tls/ldclient_tls_options_SUITE.erl index b834ae5..15c4a6d 100644 --- a/test-tls/ldclient_tls_options_SUITE.erl +++ b/test-tls/ldclient_tls_options_SUITE.erl @@ -92,12 +92,12 @@ open_stream(Uri, HttpOptions, SdkKeyEnv) -> shotgun:parse_event(Bin) catch Code:Reason -> % Exception when processing event, log error, close connection - error_logger:warning_msg("Invalid SSE event error (~p): ~p", [Code, Reason]), + logger:warning("Invalid SSE event error (~p): ~p", [Code, Reason], #{domain => [ldclient]}), shotgun:close(Pid) end; (fin, _Ref, _Bin) -> % Connection ended, close monitored shotgun client pid, so we can reconnect - error_logger:warning_msg("Streaming connection ended"), + logger:warning("Streaming connection ended", #{domain => [ldclient]}), shotgun:close(Pid) end, Options = #{async => true, async_mode => sse, handle_event => F}, diff --git a/test/ldclient_clause_SUITE.erl b/test/ldclient_clause_SUITE.erl index b454289..8f86986 100644 --- a/test/ldclient_clause_SUITE.erl +++ b/test/ldclient_clause_SUITE.erl @@ -285,7 +285,7 @@ check_operators(_) -> true = case Result =:= ldclient_clause:match_context(Clause, Context, null, null) of true -> true; false -> - error_logger:error_msg("User context. Op ~p, ContextValue $p, ClauseValue ~p Expected ~p", [ + logger:error("User context. Op ~p, ContextValue $p, ClauseValue ~p Expected ~p", [ Operator, ContextValue, ClauseValue, @@ -308,7 +308,7 @@ check_operators(_) -> true = case Result =:= ldclient_clause:match_context(Clause, Context, null, null) of true -> true; false -> - error_logger:error_msg("Non-user context. Op ~p, ContextValue $p, ClauseValue ~p Expected ~p", [ + logger:error("Non-user context. Op ~p, ContextValue $p, ClauseValue ~p Expected ~p", [ Operator, ContextValue, ClauseValue, @@ -332,7 +332,7 @@ check_operators(_) -> true = case no_match =:= ldclient_clause:match_context(Clause, Context, null, null) of true -> true; false -> - error_logger:error_msg("Mistmatched context kinds, should never match." + logger:error("Mistmatched context kinds, should never match." " Op ~p, ContextValue $p, ClauseValue ~p Expected ~p", [ Operator, ContextValue, diff --git a/test/ldclient_sdk_key_logging_SUITE.erl b/test/ldclient_sdk_key_logging_SUITE.erl index 20df942..77f914a 100644 --- a/test/ldclient_sdk_key_logging_SUITE.erl +++ b/test/ldclient_sdk_key_logging_SUITE.erl @@ -17,6 +17,9 @@ streaming_connection_error_does_not_log_sdk_key/1 ]). +%% logger handler callback +-export([log/2]). + %%==================================================================== %% ct functions %%==================================================================== @@ -37,14 +40,14 @@ end_per_suite(_) -> ok = application:stop(ldclient). init_per_testcase(_, Config) -> - % Install a custom error_logger handler to capture logs - error_logger:tty(false), - error_logger:add_report_handler(?MODULE, self()), + % Install a custom logger handler to capture logs + HandlerId = ldclient_sdk_key_test_handler, + ok = logger:add_handler(HandlerId, ?MODULE, #{config => #{parent => self()}}), Config. end_per_testcase(_, _Config) -> - error_logger:delete_report_handler(?MODULE), - error_logger:tty(true), + HandlerId = ldclient_sdk_key_test_handler, + ok = logger:remove_handler(HandlerId), ok. %%==================================================================== @@ -178,38 +181,14 @@ flush_messages() -> end. %%==================================================================== -%% error_logger handler callbacks +%% logger handler callback %%==================================================================== -init(Parent) -> - {ok, Parent}. - -handle_event({error, _GL, {_Pid, Format, Data}}, Parent) -> - Parent ! {error_log, Format, Data}, - {ok, Parent}; -handle_event({error_report, _GL, {_Pid, _Type, Report}}, Parent) -> - Parent ! {error_report, Report}, - {ok, Parent}; -handle_event({warning_msg, _GL, {_Pid, Format, Data}}, Parent) -> - Parent ! {warning_log, Format, Data}, - {ok, Parent}; -handle_event({warning_report, _GL, {_Pid, _Type, Report}}, Parent) -> - Parent ! {warning_report, Report}, - {ok, Parent}; -handle_event({info_msg, _GL, {_Pid, Format, Data}}, Parent) -> - Parent ! {info_log, Format, Data}, - {ok, Parent}; -handle_event(_Event, Parent) -> - {ok, Parent}. - -handle_call(_Request, State) -> - {ok, ok, State}. - -handle_info(_Info, State) -> - {ok, State}. - -terminate(_Reason, _State) -> +log(#{level := Level, msg := Msg}, #{config := #{parent := Parent}}) -> + FormattedMsg = case Msg of + {string, S} -> S; + {report, Report} -> io_lib:format("~p", [Report]); + {Format, Args} -> io_lib:format(Format, Args) + end, + Parent ! {log, Level, FormattedMsg}, ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. From 47e8a268dbce66e4d70e1d3abfe58315f5179a38 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:57:15 -0700 Subject: [PATCH 2/3] fix: Remove trailing ~n from logger format strings The default logger_formatter templates already append newlines, so trailing ~n in format strings produced double-newlines. Additionally, one 2-argument logger:info call had ~n in a literal string where it would not be expanded at all (unlike error_logger which processed all strings through io_lib:format). --- src/ldclient_testdata.erl | 2 +- src/ldclient_update_file_server.erl | 4 ++-- src/ldclient_update_null_server.erl | 2 +- src/ldclient_update_stream_server.erl | 10 +++++----- src/ldclient_update_testdata_server.erl | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ldclient_testdata.erl b/src/ldclient_testdata.erl index ea8c7d8..50f9232 100644 --- a/src/ldclient_testdata.erl +++ b/src/ldclient_testdata.erl @@ -272,7 +272,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, _State) -> - logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), + logger:info("Terminating, reason: ~p; Pid none", [Reason], #{domain => [ldclient]}), ok. %% @doc diff --git a/src/ldclient_update_file_server.erl b/src/ldclient_update_file_server.erl index 79415f4..911674e 100644 --- a/src/ldclient_update_file_server.erl +++ b/src/ldclient_update_file_server.erl @@ -94,10 +94,10 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{timer_ref := undefined} = _State) -> - logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), + logger:info("Terminating, reason: ~p; Pid none", [Reason], #{domain => [ldclient]}), ok; terminate(Reason, #{timer_ref := TimerRef} = _State) -> - logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), + logger:info("Terminating, reason: ~p; Pid none", [Reason], #{domain => [ldclient]}), _ = timer:cancel(TimerRef), ok. diff --git a/src/ldclient_update_null_server.erl b/src/ldclient_update_null_server.erl index 7f4f4a7..997f0ba 100644 --- a/src/ldclient_update_null_server.erl +++ b/src/ldclient_update_null_server.erl @@ -64,7 +64,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, _State) -> - logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), + logger:info("Terminating, reason: ~p; Pid none", [Reason], #{domain => [ldclient]}), ok. code_change(_OldVsn, State, _Extra) -> diff --git a/src/ldclient_update_stream_server.erl b/src/ldclient_update_stream_server.erl index 8bc061e..a8cf5c5 100644 --- a/src/ldclient_update_stream_server.erl +++ b/src/ldclient_update_stream_server.erl @@ -94,10 +94,10 @@ handle_info({'DOWN', _Mref, process, ShotgunPid, Reason}, #{conn := ShotgunPid, _ = ldclient_backoff:fire(NewBackoff), % Reason from DOWN message could contain connection details with headers/SDK keys SafeReason = ldclient_key_redaction:format_shotgun_error(Reason), - logger:warning("Got DOWN message from shotgun pid with reason: ~s, will retry in ~p ms~n", [SafeReason, maps:get(current, NewBackoff)], #{domain => [ldclient]}), + logger:warning("Got DOWN message from shotgun pid with reason: ~s, will retry in ~p ms", [SafeReason, maps:get(current, NewBackoff)], #{domain => [ldclient]}), {noreply, State#{conn := undefined, backoff := NewBackoff}}; handle_info({timeout, _TimerRef, listen}, State) -> - logger:info("Reconnecting streaming connection...~n", #{domain => [ldclient]}), + logger:info("Reconnecting streaming connection...", #{domain => [ldclient]}), NewState = do_listen(State), {noreply, NewState}; handle_info(_Info, State) -> @@ -106,10 +106,10 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{conn := undefined} = _State) -> - logger:info("Terminating, reason: ~p; Pid none~n", [Reason], #{domain => [ldclient]}), + logger:info("Terminating, reason: ~p; Pid none", [Reason], #{domain => [ldclient]}), ok; terminate(Reason, #{conn := ShotgunPid} = _State) -> - logger:info("Terminating streaming connection, reason: ~p; Pid ~p~n", [Reason, ShotgunPid], #{domain => [ldclient]}), + logger:info("Terminating streaming connection, reason: ~p; Pid ~p", [Reason, ShotgunPid], #{domain => [ldclient]}), ok = shotgun:close(ShotgunPid). code_change(_OldVsn, State, _Extra) -> @@ -136,7 +136,7 @@ do_listen(#{ {error, permanent, Reason} -> % Reason here is already safe: either a sanitized string from format_shotgun_error % or an integer status code from the do_listen/5 method. - logger:error("Stream encountered permanent error ~p, giving up~n", [Reason], #{domain => [ldclient]}), + logger:error("Stream encountered permanent error ~p, giving up", [Reason], #{domain => [ldclient]}), State; {ok, Pid} -> NewBackoff = ldclient_backoff:succeed(Backoff), diff --git a/src/ldclient_update_testdata_server.erl b/src/ldclient_update_testdata_server.erl index de8e2c7..d0e1afa 100644 --- a/src/ldclient_update_testdata_server.erl +++ b/src/ldclient_update_testdata_server.erl @@ -74,7 +74,7 @@ handle_info(_Info, State) -> -spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: state()) -> term(). terminate(Reason, #{ tag := Tag, test_data_server := TestDataServer }) -> - logger:info("Terminating, reason: ~p; Pid: ~p ~n", [Reason, self()], #{domain => [ldclient]}), + logger:info("Terminating, reason: ~p; Pid: ~p", [Reason, self()], #{domain => [ldclient]}), gen_server:call(TestDataServer, {unregister_instance, Tag}), ok. From a7d7962370922b01af053b719052c51a4626c68c Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 19 Mar 2026 08:40:59 -0700 Subject: [PATCH 3/3] fix: Correct format specifier %p to ~p in poll server log Pre-existing bug: the format string used C-style %p instead of Erlang's ~p for the Uri argument. This caused io_lib:format to fail at runtime, silently losing the "Failed to connect to update server" warning message. --- src/ldclient_update_poll_server.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ldclient_update_poll_server.erl b/src/ldclient_update_poll_server.erl index 59faf45..048436b 100644 --- a/src/ldclient_update_poll_server.erl +++ b/src/ldclient_update_poll_server.erl @@ -142,7 +142,7 @@ process_response({error, {bad_status, StatusCode, Reason}}, _, _, Uri) when Stat logger:warning("Unexpected response code: ~p when polling for updates at URL ~p: ~p.", [StatusCode, Uri, Reason], #{domain => [ldclient]}), ok; process_response({error, network_error}, _, _, Uri) -> - logger:warning("Failed to connect to update server at: %p", [Uri], #{domain => [ldclient]}), + logger:warning("Failed to connect to update server at: ~p", [Uri], #{domain => [ldclient]}), ok; process_response({ok, not_modified}, _, _, _) -> ok; process_response({ok, ResponseBody}, FeatureStore, Tag, _) ->