|
| 1 | +Getting a Stack Trace |
| 2 | +===================== |
| 3 | + |
| 4 | +When :doc:`reporting a bug </contributing/code/bugs>` for an |
| 5 | +exception or a wrong behavior in code, it is crucial that you provide |
| 6 | +one or several stack traces. To understand why, you first have to |
| 7 | +understand what a stack trace is, and how it can be useful to you as a |
| 8 | +developer, and also to library maintainers. |
| 9 | + |
| 10 | +Anatomy of a Stack Trace |
| 11 | +------------------------ |
| 12 | + |
| 13 | +A stack trace is called that way because it allows one to see a trail of |
| 14 | +function calls leading to a point in code since the beginning of the |
| 15 | +program. That point is not necessarily an exception. For instance, you |
| 16 | +can use the native PHP function ``debug_print_backtrace()`` to get such |
| 17 | +a trace. For each line in the trace, you get a file and a function or |
| 18 | +method call, and the line number for that call. This is often of great |
| 19 | +help for understanding the flow of your program and how it can end up in |
| 20 | +unexpected places, such as lines of code where exceptions are thrown. |
| 21 | + |
| 22 | +Stack Traces and Exceptions |
| 23 | +--------------------------- |
| 24 | + |
| 25 | +In PHP, every exception comes with its own stack trace, which is |
| 26 | +displayed by default if the exception is not caught. When using Symfony, |
| 27 | +such exceptions go through a custom exception handler, which enhances |
| 28 | +them in various ways before displaying them according to the current |
| 29 | +Server API (CLI or not). |
| 30 | +This means a better way to get a stack trace when you do need the |
| 31 | +program to continue is to throw an exception, as follows: |
| 32 | +``throw new \Exception();`` |
| 33 | + |
| 34 | +Nested Exceptions |
| 35 | +----------------- |
| 36 | + |
| 37 | +When applications get bigger, complexity is often tackled with layers of |
| 38 | +architecture that need to be kept separate. For instance, if you have a |
| 39 | +web application that makes a call to a remote API, it might be good to |
| 40 | +wrap exceptions thrown when making that call with exceptions that have |
| 41 | +special meaning in your domain, and to build appropriate HTTP exceptions |
| 42 | +from those. Exceptions can be nested by using the ``$previous`` |
| 43 | +argument that appears in the signature of the ``Exception`` class: |
| 44 | +``public __construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )`` |
| 45 | +This means that sometimes, when you get an exception from an |
| 46 | +application, you might actually get several of them. |
| 47 | + |
| 48 | +What to look for in a Stack Trace |
| 49 | +--------------------------------- |
| 50 | + |
| 51 | +When using a library, you will call code that you did not write. When |
| 52 | +using a framework, it is the opposite: because you follow the |
| 53 | +conventions of the framework, `the framework finds your code and calls |
| 54 | +it <https://en.wikipedia.org/wiki/Inversion_of_control>`_, and does |
| 55 | +things for you beforehand, like routing or access control. |
| 56 | +Symfony being both a framework and library of components, it calls your |
| 57 | +code and then your code might call it. This means you will always have |
| 58 | +at least 2 parts, very often 3 in your stack traces when using Symfony: |
| 59 | +a part that starts in one of the entrypoints of the framework |
| 60 | +(``bin/console`` or ``public/index.php`` in most cases), and ends when |
| 61 | +reaching your code, most times in a command or in a controller found under |
| 62 | +``src``. Then, either the exception is thrown in your code or in |
| 63 | +libraries you call. If it is the latter, there should be a third part in |
| 64 | +the stack trace with calls made in files under ``vendor``. Before |
| 65 | +landing in that directory, code goes through numerous review processes |
| 66 | +and CI pipelines, which means it should be less likely to be the source |
| 67 | +of the issue than code from your application, so it is important that |
| 68 | +you focus first on lines starting with ``src``, and look for anything |
| 69 | +suspicious or unexpected, like method calls that are not supposed to |
| 70 | +happen. |
| 71 | + |
| 72 | +Next, you can have a look at what packages are involved. Files under |
| 73 | +``vendor`` are organized by Composer in the following way: |
| 74 | +``vendor/acme/router`` where ``acme`` is the vendor, ``router`` the |
| 75 | +library and ``acme/router`` the Composer package. If you plan on |
| 76 | +reporting the bug, make sure to report it to the library throwing the |
| 77 | +exception. ``composer home acme/router`` should lead you to the right |
| 78 | +place for that. As Symfony is a monorepository, use ``composer home |
| 79 | +symfony/symfony`` when reporting a bug for any component. |
| 80 | + |
| 81 | +Getting Stack Traces with Symfony |
| 82 | +--------------------------------- |
| 83 | + |
| 84 | +Now that we have all this in mind, let us see how to get a stack trace |
| 85 | +with Symfony. |
| 86 | + |
| 87 | +Stack Traces in your Web Browser |
| 88 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 89 | + |
| 90 | +Several things need to be paid attention to when picking a stack trace |
| 91 | +from your development environment through a web browser: |
| 92 | + |
| 93 | +1. Are there several exceptions? If yes, the most interesting one is |
| 94 | + often exception 1/n which, is shown _last_ in the example below (it |
| 95 | + is the one marked as exception [1/2]). |
| 96 | +2. Under the "Stack Traces" tab, you will find exceptions in plain |
| 97 | + text, so that you can easily share them in e.g. bug reports. Make |
| 98 | + sure to **remove any sensitive information** before doing so. |
| 99 | +3. You may notice there is a logs tab too; this tab does not have to do |
| 100 | + with stack traces, it only contains logs produced in arbitrary places |
| 101 | + in your application. They may or may not relate to the exception you |
| 102 | + are getting, but are not what the term "stack trace" refers to. |
| 103 | + |
| 104 | +.. image:: /_images/contributing/code/stack-trace.gif |
| 105 | + :align: center |
| 106 | + :class: with-browser |
| 107 | + |
| 108 | +Since stack traces may contain sensitive data, they should not be |
| 109 | +exposed in production. Getting a stack trace from your production |
| 110 | +environment, although more involving, is still possible with solutions |
| 111 | +that include but are not limited to sending them to an email address |
| 112 | +with monolog. |
| 113 | + |
| 114 | +Stack Traces in the CLI |
| 115 | +~~~~~~~~~~~~~~~~~~~~~~~ |
| 116 | + |
| 117 | +Exceptions might occur when running a Symfony command. By default, only |
| 118 | +the message is shown because it is often enough to understand what is |
| 119 | +going on: |
| 120 | + |
| 121 | +.. code-block:: terminal |
| 122 | +
|
| 123 | + $ php bin/console debug:exception |
| 124 | +
|
| 125 | +
|
| 126 | + Command "debug:exception" is not defined. |
| 127 | +
|
| 128 | + Did you mean one of these? |
| 129 | + debug:autowiring |
| 130 | + debug:config |
| 131 | + debug:container |
| 132 | + debug:event-dispatcher |
| 133 | + debug:form |
| 134 | + debug:router |
| 135 | + debug:translation |
| 136 | + debug:twig |
| 137 | +
|
| 138 | +
|
| 139 | +If that is not the case, you can obtain a stack trace by increasing the |
| 140 | +:doc:`verbosity level</console/verbosity>` with ``--verbose``: |
| 141 | + |
| 142 | +.. code-block:: terminal |
| 143 | +
|
| 144 | + $ php bin/console --verbose debug:exception |
| 145 | +
|
| 146 | + In Application.php line 644: |
| 147 | +
|
| 148 | + [Symfony\Component\Console\Exception\CommandNotFoundException] |
| 149 | + Command "debug:exception" is not defined. |
| 150 | +
|
| 151 | + Did you mean one of these? |
| 152 | + debug:autowiring |
| 153 | + debug:config |
| 154 | + debug:container |
| 155 | + debug:event-dispatcher |
| 156 | + debug:form |
| 157 | + debug:router |
| 158 | + debug:translation |
| 159 | + debug:twig |
| 160 | +
|
| 161 | +
|
| 162 | + Exception trace: |
| 163 | + at /app/vendor/symfony/console/Application.php:644 |
| 164 | + Symfony\Component\Console\Application->find() at /app/vendor/symfony/framework-bundle/Console/Application.php:116 |
| 165 | + Symfony\Bundle\FrameworkBundle\Console\Application->find() at /app/vendor/symfony/console/Application.php:228 |
| 166 | + Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/framework-bundle/Console/Application.php:82 |
| 167 | + Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:140 |
| 168 | + Symfony\Component\Console\Application->run() at /app/bin/console:42 |
| 169 | +
|
| 170 | +Stack Traces and API Calls |
| 171 | +~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 172 | + |
| 173 | +When getting an exception from an API, you might not get a stack trace, |
| 174 | +or it might be displayed in a way that is not suitable for sharing. |
| 175 | +Luckily, when in the dev environment, you can obtain a plain text stack |
| 176 | +trace by using the profiler. To find the profile, you can have a look |
| 177 | +at the ``X-Debug-Token-Link`` response headers: |
| 178 | + |
| 179 | +.. code-block:: terminal |
| 180 | +
|
| 181 | + $ curl --head http://localhost:8000/api/posts/1 |
| 182 | + … more headers |
| 183 | + X-Debug-Token: 110e1e |
| 184 | + X-Debug-Token-Link: http://localhost:8000/_profiler/110e1e |
| 185 | + X-Robots-Tag: noindex |
| 186 | + X-Previous-Debug-Token: 209101 |
| 187 | +
|
| 188 | +Following that link will lead you to a page very similar to the one |
| 189 | +described above in `Stack Traces in your Web Browser`_. |
0 commit comments