Skip to content

Enable nushell error with backtrace#14945

Merged
WindSoilder merged 17 commits intonushell:mainfrom
WindSoilder:traceback_without_engine_state
Feb 6, 2025
Merged

Enable nushell error with backtrace#14945
WindSoilder merged 17 commits intonushell:mainfrom
WindSoilder:traceback_without_engine_state

Conversation

@WindSoilder
Copy link
Copy Markdown
Contributor

@WindSoilder WindSoilder commented Jan 28, 2025

Description

After this pr, nushell is able to raise errors with a backtrace, which should make users easier to debug. To enable the feature, users need to set env variable via $env.NU_BACKTRACE = 1. But yeah it might not work perfectly, there are some corner cases which might not be handled.

I think it should close #13379 in another way.

About the change

The implementation mostly contained with 2 parts:

  1. introduce a new ChainedError struct as well as a new ShellError::ChainedError variant. If eval_instruction returned an error, it converts the error to ShellError::ChainedError.
    ChainedError struct is responsable to display errors properly. It needs to handle the following 2 cases:

    • if we run a function which runs error make internally, it needs to display the error itself along with caller span.
    • if we run a error make directly, or some commands directly returns an error, we just want nushell raise an error about error make.
  2. Attach caller spans to ListStream and ByteStream, because they are lazy streams, and only contains the span that runs it directly(like ^false, for example), so nushell needs to add all caller spans to the stream.
    For example: in def a [] { ^false }; def b [] { a; 33 }; b, when we run b, which runs a, which runs ^false, the ByteStream only contains the span of ^false, we need to make it contains the span of a, so nushell is able to get all spans if something bad happened.
    This behavior is happened after running Instruction::Call, if it returns a ByteStream and ListStream, it will call push_caller_span method to attach call spans.

User-Facing Changes

It's better to demostrate how it works by examples, given the following definition:

> $env.NU_BACKTRACE = 1
> def a [x] { if $x == 3 { error make {msg: 'a custom error'}}}
> def a_2 [x] { if $x == 3 { ^false } else { $x } }
> def a_3 [x] { if $x == 3 { [1 2 3] | each {error make {msg: 'a custom error inside list stream'} } } }
> def b [--list-stream --external] {
    if $external == true {
        # error with non-zero exit code, which is generated from external command.
        a_2 1; a_2 3; a_2 2
    } else if $list_stream == true {
        # error generated by list-stream
        a_3 1; a_3 3; a_3 2
    } else {
        # error generated by command directly
        a 1; a 2; a 3
    }
}

Run b directly shows the following error:

Details
Error: chained_error

  × oops
   ╭─[entry #27:1:1]
 1  b
   · 
   · ╰── error happened when running this
   ╰────

Error: chained_error

  × oops
    ╭─[entry #26:10:19]
  9          # error generated by command directly
 10          a 1; a 2; a 3
    ·                   
    ·                   ╰── error happened when running this
 11      }
    ╰────

Error:
  × a custom error
   ╭─[entry #6:1:26]
 1  def a [x] { if $x == 3 { error make {msg: 'a custom error'}}}
   ·                          ─────┬────
   ·                               ╰── originates from here
   ╰────

Run b --list-stream shows the following error

Details
Error: chained_error

  × oops
   ╭─[entry #28:1:1]
 1  b --list-stream
   · 
   · ╰── error happened when running this
   ╰────

Error: nu::shell::eval_block_with_input

  × Eval block failed with pipeline input
   ╭─[entry #26:7:16]
 6          # error generated by list-stream
 7          a_3 1; a_3 3; a_3 2
   ·                ─┬─
   ·                 ╰── source value
 8      } else {
   ╰────

Error: nu::shell::eval_block_with_input

  × Eval block failed with pipeline input
   ╭─[entry #23:1:29]
 1  def a_3 [x] { if $x == 3 { [1 2 3] | each {error make {msg: 'a custom error inside list stream'} } } }
   ·                             
   ·                             ╰── source value
   ╰────

Error:
  × a custom error inside list stream
   ╭─[entry #23:1:44]
 1  def a_3 [x] { if $x == 3 { [1 2 3] | each {error make {msg: 'a custom error inside list stream'} } } }
   ·                                            ─────┬────
   ·                                                 ╰── originates from here
   ╰────

Run b --external shows the following error:

Details
Error: chained_error

  × oops
   ╭─[entry #29:1:1]
 1  b --external
   · 
   · ╰── error happened when running this
   ╰────

Error: nu::shell::eval_block_with_input

  × Eval block failed with pipeline input
   ╭─[entry #26:4:16]
 3          # error with non-zero exit code, which is generated from external command.
 4          a_2 1; a_2 3; a_2 2
   ·                ─┬─
   ·                 ╰── source value
 5      } else if $list_stream == true {
   ╰────

Error: nu::shell::non_zero_exit_code

  × External command had a non-zero exit code
   ╭─[entry #7:1:29]
 1  def a_2 [x] { if $x == 3 { ^false } else { $x } }
   ·                             ──┬──
   ·                               ╰── exited with code 1
   ╰────

It also added a message to guide the usage of NU_BACKTRACE, see the last line in the following example:

 ls asdfasd
Error: nu::shell::io::not_found

  × I/O error
  ╰─▶   × Entity not found

   ╭─[entry #17:1:4]
 1 │ ls asdfasd
   ·    ───┬───
   ·       ╰── Entity not found
   ╰────
  help: The error occurred at '/home/windsoilder/projects/nushell/asdfasd'

set the `NU_BACKTRACE=1` environment variable to display a backtrace.

Tests + Formatting

Added some tests for the behavior.

After Submitting

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Jan 28, 2025

This is very cool!

@WindSoilder WindSoilder force-pushed the traceback_without_engine_state branch from 2a14591 to 4862a76 Compare January 28, 2025 23:50
@WindSoilder WindSoilder force-pushed the traceback_without_engine_state branch from 4862a76 to 681fec1 Compare February 2, 2025 00:02
@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Feb 6, 2025

I'm up for trying this when you are.

@WindSoilder
Copy link
Copy Markdown
Contributor Author

Thanks! Let's land this.

@WindSoilder WindSoilder merged commit 2f18b9c into nushell:main Feb 6, 2025
15 checks passed
@github-actions github-actions bot added this to the v0.103.0 milestone Feb 6, 2025
@WindSoilder WindSoilder deleted the traceback_without_engine_state branch February 6, 2025 14:27
@NotTheDr01ds
Copy link
Copy Markdown
Contributor

I like this, but I don't think it's the solution for #13379. I'm guessing #13379 got auto-closed based on the mention above, but not sure.

Custom-command authors who are writing a command for external consumption (e.g., std, for instance) need to be able to generate a user-friendly error that references the custom command or its arguments. With this, it seems the error make is still the first in the chain, and the others don't show up until after the backtrace is enabled?

WindSoilder pushed a commit that referenced this pull request Feb 20, 2025
# Description

Resolves #15070 by removing the `BACKTRACE` message from all Nushell
(non-panic) errors. This was added in #14945 and is useful for
debugging, but not all that helpful to the typical shell user,
especially since most shell errors won't have a backtrace anyway.

At some point it would be nice to display this message only when there
*is* a backtrace available.

# User-Facing Changes

Error messages will be more concise.

# Tests + Formatting

Updated tests.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

We should include information in the *"Custom Commands"* chapter of the
documentation on how to enable this for debugging.
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.

error make ability to reference caller span

4 participants