You don't get an inscrutable message. An example from Elixir docs:
iex> {a, b, c} = {:hello, "world"}
* (MatchError) no match of right hand side value: {:hello, "world"}
The MatchError shows you the part that was not matching. When pattern matching on {:ok, value}, on match error you'd be told it doesn't match, say {:error, %DatabaseError{reason: "Server has gone away."}}
I always pair my production Elixir apps with Sentry, which gives me a backtrace, so I have everything needed to track down an error.
> But if you're using a library, most of the time, use the raising version.
I use the raising version to explicitly "I want this to crash." So this would happen only at the edges of the system. All internal code should operate on tuples. You don't want a core, internal function to raise and have to catch it on higher-level modules, because that's terrible form. In other words, feel free to raise, as long as there is no one that has to catch the exception. If you raise, the goal is to crash, not to leverage exceptions as control flow. This isn't Java.
I've been using Elixir since 2016 and I don't think I have written more than a couple exception handlers since.