YakovL
YakovL

Reputation: 8347

How to "inspect to file" (or to string) in Elixir?

In Elixir, we can IO.inspect anyStructure to get anyStructure's internals printed to output. Is there a similar method to output it to a file (or, as a more flexible solution, to a string)?

I've looked through some articles on debugging and io but don't see a solution. I've also tried

{:ok, file} = File.open("test.log", [:append, {:delayed_write, 100, 20}])
structure = %{ a: 1, b: 2 }
IO.binwrite(file, structure)
File.close file

but that results in

no function clause matching in IO.binwrite/2 [...]
def binwrite(device, iodata) when is_list(iodata) or is_binary(iodata)

I’ve also googled some "elixir serialize" and "elixir object to string", but haven't found anything useful (like :erlang.term_to_binary which returns, well, binary). Is there a simple way to get the same result that IO.inspect prints, into a file or a string?

Upvotes: 4

Views: 1819

Answers (4)

Mikhail Aksenov
Mikhail Aksenov

Reputation: 571

There is already inspect/2 function (not the same as IO.inspect), just go with it:

#> inspect({1,2,3})
"{1, 2, 3}"

#> h inspect/2
                         def inspect(term, opts \\ [])

  @spec inspect(
          Inspect.t(),
          keyword()
        ) :: String.t()

Inspects the given argument according to the Inspect protocol. The second
argument is a keyword list with options to control inspection.


You can do whatever you wish with the string afterwards.

Upvotes: 6

Adam Millerchip
Adam Millerchip

Reputation: 23129

In Elixir, we can IO.inspect anyStructure to get anyStructure's internals printed to output.

This is not quite true; IO.inspect uses the Inspect protocol. What you see is not the internals of the struct, but whatever that struct's implementation of the Inspect protocol is written to produce. There are different options you can give to inspect, defined in Inspect.Opts, one of them is structs: false, which will print structs as maps.

For example, inspecting a range struct:

iex> inspect(1..10)
"1..10"
iex> inspect(1..10, structs: false)
"%{__struct__: Range, first: 1, last: 10, step: 1}"

To answer your question and to add to the other answers, here is a method that uses File.open!/3 to reuse an open file and log multiple inspect calls to the same file, then close the file:

File.open!("test.log", [:write], fn file ->
  IO.inspect(file, %{ a: 1, b: 2 }, [])
  IO.inspect(file, "logging a string", [])
  IO.inspect(file, DateTime.utc_now!(), [])
  IO.inspect(file, DateTime.utc_now!(), structs: false)
end)

This produces the following test.log file:

%{a: 1, b: 2}
"logging a string"
~U[2022-04-29 09:51:46.467338Z]
%{
  __struct__: DateTime,
  calendar: Calendar.ISO,
  day: 29,
  hour: 9,
  microsecond: {485474, 6},
  minute: 51,
  month: 4,
  second: 46,
  std_offset: 0,
  time_zone: "Etc/UTC",
  utc_offset: 0,
  year: 2022,
  zone_abbr: "UTC"
}

Upvotes: 3

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

You simply need to combine inspect/2 which returns a binary and File.write/3 or any other function dumping to a file.

File.write("test.log", inspect(%{a: 1, b: 2}, limit: :infinity))

Note the limit: :infinity option, without it the long structures will be truncated for better readability when inspecting to stdout.

Upvotes: 2

tessi
tessi

Reputation: 13574

You can give IO.inspect an additional param to tell it where to write to:

{:ok, pid} = StringIO.open("")
IO.inspect(pid, %{test: "data"}, label: "IO.inspect options work too \o/")
{:ok, {_in, out}} = StringIO.close(pid)

out # "IO.inspect options work too o/: %{test: \"data\"}\n"

It accepts a pid of a process to write to. StringIO provides such a process, returning you a string on close.

Upvotes: 3

Related Questions