Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better error formatting #132

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Conversation

dkuku
Copy link
Contributor

@dkuku dkuku commented Nov 29, 2022

This library is very usefull but I find the errors always so hard to read when it's inside a large struct because there are no newlines and when it's wrapped on 100 lines it looks like this (it's a simple example - this can be multiple screens long when refactoring legacy code):

     ** (Hammox.TypeMatchError) 
    Returned value {:ok, %Customers.Schemas.Customer{__meta__: #Ecto.Schema.Metadata<:built, "custom
ers">, first_name: "John", last_name: "Doe"}, []} does not match type {:ok, Customers.Schemas.Custom
er.t(), [integer()]} | {:ok, :no_customers} | {:error, :not_found}.
       Value {:ok, %Customers.Schemas.Customer{__meta__: #Ecto.Schema.Metadata<:built, "customers">,
 first_name: "John", last_name: "Doe"}, []} does not match type {:ok, Customers.Schemas.Customer.t()
, [integer()]} | {:ok, :no_customers} | {:error, :not_found}.
         2nd tuple element %Customers.Schemas.Customer{__meta__: #Ecto.Schema.Metadata<:built, "cust
omers">, first_name: "John", last_name: "Doe"} does not match 2nd element type Customers.Schemas.Cus
tomer.t().
           Map value "48 22 333 33 33" for key :contact_number does not match map value type EctoPho
neNumber.t() | nil.
             Value "48 22 333 33 33" does not match type EctoPhoneNumber.t() | nil.

I added some newlines and pretty printed the inspected structures - if it's a problem the inspect params can be set also in config.
Another useful feature would be different colors for data and messages.

     ** (Hammox.TypeMatchError) 
     Returned value {:ok,
      %Customers.Schemas.Customer{
        __meta__: #Ecto.Schema.Metadata<:built, "customers">,
        first_name: "John",
        last_name: "Doe"
      }, []} does not match type 
     {:ok, Customers.Schemas.Customer.t(), [integer()]}
      | {:ok, :no_customers}
      | {:error, :not_found}
     
       Value {:ok,
        %Customers.Schemas.Customer{
          __meta__: #Ecto.Schema.Metadata<:built, "customers">,
          first_name: "John",
          last_name: "Doe"
        }, []} does not match type 
       {:ok, Customers.Schemas.Customer.t(), [integer()]}
        | {:ok, :no_customers}
        | {:error, :not_found}
     
         2nd tuple element %Customers.Schemas.Customer{
          __meta__: #Ecto.Schema.Metadata<:built, "customers">,
          first_name: "John",
          last_name: "Doe"
         } does not match 2nd element type 
         Customers.Schemas.Customer.t().
     
           Map value "48 22 333 33 33" for key :contact_number does not match map value type 
           EctoPhoneNumber.t()
            | nil.
     
             Value "48 22 333 33 33" does not match type 
             EctoPhoneNumber.t()
              | nil.

Copy link

@mpmiszczyk mpmiszczyk left a comment

Choose a reason for hiding this comment

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

Introducing the preatty print was in my mind for quite some time.

To be honest I'm not sure if it wouldn't be easier just to rething the error message into something simpler by starting inspects from the beggining of the line:

     ** (Hammox.TypeMatchError) 
     Returned value does not match type 
     returned:
     {:ok, 
      %Customers.Schemas.Customer{
        __meta__: #Ecto.Schema.Metadata<:built, "customers">,
        first_name: "John",
        last_name: "Doe"
      }, []}
     type: 
     {:ok, Customers.Schemas.Customer.t(), [integer()]}
      | {:ok, :no_customers}
      | {:error, :not_found}
     
       Value does not match type
       retuned:
       {:ok,
        %Customers.Schemas.Customer{
          __meta__: #Ecto.Schema.Metadata<:built, "customers">,
          first_name: "John",
          last_name: "Doe"
        }, []}
       type: 
       {:ok, Customers.Schemas.Customer.t(), [integer()]}
        | {:ok, :no_customers}
        | {:error, :not_found}
....

I believe that could be achieved with

  • human_reason returning multi-line strings
  • original left_pad
  • and without the need of putting padding into process memory

@dkuku
Copy link
Contributor Author

dkuku commented Nov 30, 2022

human_reason returning multi-line strings
original left_pad
and without the need of putting padding into process memory

I had this implemented but decided to not to modify the original messages for now.
I don't think the padding is needed at all when the message is properly formatted with new lines and some dividers - it may be useful for simple cases and short messages but with nested structs it does not work, even some of existing messges are too long to fit in one line and break the layout.

@msz
Copy link
Owner

msz commented Dec 27, 2022

Thanks for the PR!

The current Hammox messages are definitely quite unreadable and they do need work. Your proposal at a glance seems like a big readability improvement!

However I'm thinking about what you mentioned:

(it's a simple example - this can be multiple screens long when refactoring legacy code)

I work in a big codebase and I definitely experience this. There are not only very long stack traces, but also many tests in the suite failing and printing them. In this situation, I'm not sure we'd want to enforce pretty printing, as it would make the output so much longer.

Additionally, I think there is a deeper problem of displaying too much information in the first place. Currently the logic is pretty basic and we print whole structs for comparison. A better way to tackle the readability problem could be to be smarter about what is printed from the struct and decrease the total output. From this angle, pretty printing could potentially make the problem worse because it's easier to choose structs you want to dig into when they are on one line compared to them taking big chunks of screen space.

if it's a problem the inspect params can be set also in config.

I think this could be a reasonable way forward. A pretty: true config option for Hammox could allow us to experiment with and develop a pretty mode for those that prefer it while not changing the current default. What do you think?

@dkuku
Copy link
Contributor Author

dkuku commented Dec 27, 2022

Config option may be the way to go, it should not be hard to implement. I can look into it.

@dkuku dkuku force-pushed the better_error_formatting branch 3 times, most recently from 3a5e480 to 69e2d39 Compare December 27, 2022 20:39
@dkuku
Copy link
Contributor Author

dkuku commented Dec 27, 2022

I added config and some description

@boonious
Copy link

Hey what's the status for this @msz? Could this PR be merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants