Skip to content

Fix "always evaluate" for Logger anonymous function tuples#15081

Merged
josevalim merged 1 commit intoelixir-lang:mainfrom
Odaeus:fix-anon-fn-logger-always-evaluate
Jan 28, 2026
Merged

Fix "always evaluate" for Logger anonymous function tuples#15081
josevalim merged 1 commit intoelixir-lang:mainfrom
Odaeus:fix-anon-fn-logger-always-evaluate

Conversation

@Odaeus
Copy link
Contributor

@Odaeus Odaeus commented Jan 27, 2026

My colleague discovered that many of our tests were failing on a fresh build of a project due to an exception being raised when the Quantum package attempted to use the Logger:

Reason: shutdown: failed to start child: Integrations.Scheduler
  ** (EXIT) shutdown: failed to start child: Quantum. JobBroadcaster
    ** (EXIT) an exception was raised:
      ** (Protocol.UndefinedError) protocol String.Chars not implemented for type Tuple. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Decimal, Float, Integer, Lis

Got value:

    {"Loading Initial Jobs from Config", [node: :nonode@nohost]}

      (elixir 1.18.4) lib/string/chars.ex:3: String.Chars. impl_for!/1
      (elixir 1.18.4) lib/string/chars.ex:22: String. Chars.to_string/1
      (logger 1.18.4) lib/logger.ex:982: Logger.__do_10g__/4
      (quantum 3.5.3) lib/quantum/job_broadcaster.ex:51: Quantum.JobBroadcaster.init/1

We had recently upgraded to Elixir 1.18.4 and adjusted the logging config in our test environment to:

config :logger, always_evaluate_messages: true

We traced this down to Quantum's usage of giving anonymous functions that return a tuple to Logger calls. This is an officially supported case but the code path for forced evaluation was running the function and sending the entire result tuple as data to the Logger, which is no longer supported.

I've attempted to fix this by adjusting the forced evaluation functions to handle this case. Importantly, without accidentally adding in support for passing a tuple directly.

Please let me know if this does the job!

-- Andrew

Comment on lines 1157 to 1161
{data, metadata} =
case Logger.__evaluate_log__(unquote(data)) do
{data, nil} -> {data, unquote(quoted_metadata)}
{data, metadata} -> {data, Map.new(metadata)}
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please double check the metadata handling? I think the metadata is rather merged (you can check __do_log__. It is also better if we generate less code, so I would do this:

Suggested change
{data, metadata} =
case Logger.__evaluate_log__(unquote(data)) do
{data, nil} -> {data, unquote(quoted_metadata)}
{data, metadata} -> {data, Map.new(metadata)}
end
{data, metadata} = Logger.__evaluate_log__(unquote(data), unquote(quoted_metadata))

And then __evaluate_log__ will call Enum.into(fun_meta, quoted_metadata). Please update the test so check the metadata is merged by having it actually log and then comparing the result!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is merged in the normal path. I've updated the handler as you suggested, which is more elegant.
I updated the tests to include a bit of additional metadata and I match the simple string but I'm not sure if there's the possibility of an ordering issue there. I think it's stable but please let me know if individual matches would be better.

Previously, when the configuration directive `always_evaluate_messages`
was set to true an exception was raised when a function that returns a
tuple was given to Logger:

  ```
  Logger.info(fn -> {"", []} end)
  ```

Would result in:

  ```
    ** (Protocol.UndefinedError) protocol String.Chars not implemented for Tuple

    Got value:

        {"", []}
  ```

Because the code path evaluated the function and treated the result as a
standard Logger argument. But tuples are now unsupported except as the
result of supplying a function.

Fix by allowing handling of this special case in the forced evaluation
code path which also merges the function metadata with the original
metadata.
@Odaeus Odaeus force-pushed the fix-anon-fn-logger-always-evaluate branch from 2935e67 to 6635607 Compare January 27, 2026 22:24
@josevalim josevalim merged commit ba86beb into elixir-lang:main Jan 28, 2026
11 checks passed
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants