Feature request: Could `capture_log/2` also return the result of the function?

Currently, ExUnit/CaptureLog.capture_log/2 returns the logs content captured but offers no possibility of returning any other result.

This makes it difficult to execute some_function we know should emit a log and both assert the log content and use the result of some_function.

One alternative is using the result within the function passed to capture_log, but that doesn’t compose well, or nest, and is problematic as the rest of the code could generate warnings, etc.

Another way is uses the Process dictionary, but that’s pretty ugly:

  def with_log_matching(match, fun) do
    assert ExUnit.CaptureLog.capture_log(fn ->
      result = fun.()
      Process.put(:log_capture_function_result, result)
    end) =~ match
    Process.delete(:log_capture_function_result)
  end

  test "something" do
    result = with_log_matching("Hey, something strange is going on", fn ->
      do_something_and_return(...)
    end

    assert do_something_else(result) == :ok
  end

Could we have either a new function capture_log_and_result, or else a new option for capture_log like with_result: true or similar that would return {result, "captured log..."} instead of just the captured log?

1 Like

Hi, while I’m not on the Elixir Core Team I wanted to chime in here to say that I think this is a good idea and it could simplify a common case of wanting to assert on the result of a statement as well as any logs that it generates. In particular I like the fact that it would reduce the visual code changes that are introduced when you want to assert on the log result. Currently you need to move your assert on the result into the capture_log anonymous function which makes it a little less clear (generally I prefer to keep all the asserts at the top-level).

In terms of the API I think that passing with_result: true would be a little more discoverable than a totally new function (but the core team may have different opinions of course).

1 Like

+1 that this feature would be desired. I don’t like though using an option to change the return type. My suggestion would be to introduce a new function, as the mentioned capture_log_and_result or something more concise (could we replace capture by a word that would have such meaning?).

3 Likes

with_logs

1 Like
  • bag
  • grab
  • seize
  • ensnare
  • entomb
  • nab

But yep, human languages, especially English, are imperfect.

Seize means of logging for the debugging class!

2 Likes

And entomb the enemies of observability!

1 Like

I like with_log and with_io (from the equivalent capture_io). A PR is welcome!

2 Likes

Awesome! Sounds really easy, I’ll do it :+1:

1 Like

Thank you, @marcandre, it’s already merged to master.

why not assert inside the nested fun?

    assert ExUnit.CaptureLog.capture_log(fn ->
      result = fun.()
      assert result == "foo"
    end) =~ match
1 Like

It is what I often done. Sometimes I also have sent message to self() with result to access it outside the closure.