Repository: phoenixframework/phoenix
Pull request #4201 (2021-02-06 02:23:24)

Include route information in router_dispatch exception telemetry
## The goal

@keathley and I were working on standardized metrics around our web requests on a per-route basis. We were able to do that using the `[:phoenix, :router_dispatch, :stop]` Telemetry event for requests that complete successfully, because this event includes things like the `:plug`, `:plug_opts`, and `:route` in the metadata.

## The problem

Unfortunately, for requests that crash or exit for some reason while in the application code, we got a `[:phoenix, :router_dispatch, :exception]` instead of `:stop`, which we expected, but we were surprised that some of the metadata was missing, which meant that we couldn't attribute this failure to a particular route / controller action like we wanted to.

## The workaround

We didn't like the idea, but we were able to work around this issue by capturing this metadata from the `:start` event, store it in the PDict, and pull it out later if we get the `:exception` event instead of `:stop`.

## The fix

In this PR, I've corrected what I believe was an oversight, since I see a similar pattern of `metadata = %{metadata | conn: conn}` in a few other places nearby. I didn't use this same short-hand in this case because the `:kind` and `:reason` keys don't already exist in the Map, so it raises and exception.

In the tests that I added to verify the change, I was unsure about whether we should assert that the `:log` and `:access` keys are present, because I can't tell what they even mean, but I believe the rest should be there because they can all be useful in different situations. Please let me know if you'd like me to remove those two from the tests or even filter them out from the actual metadata.

GregMefford: Hm, I can't imagine what would cause that test to fail in that way on that particular version of Elixir/OTP, but I'll pull down those versions and see if I can replicate it locally. It looks like it's failing because it received _both_ and `:exception` _and_ a `:stop` event, which I don't think should be possible.

Patch:
diff --git a/guides/controllers.md b/guides/controllers.md
index 40c5450cd..97b04385a 100644
--- a/guides/controllers.md
+++ b/guides/controllers.md
@@ -2,13 +2,13 @@
 
 > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).
+> **Requirement**: This guide expects that you have gone through the [request life-cycle guide](request_lifecycle.html).
 
-Phoenix controllers act as intermediary modules. Their functions - called actions - are invoked from the router in response to HTTP requests. The actions, in turn, gather all the necessary data and perform all the necessary steps before invoking the view layer to render a template or returning a JSON response.
+Phoenix controllers act as intermediary modules. Their functions — called actions — are invoked from the router in response to HTTP requests. The actions, in turn, gather all the necessary data and perform all the necessary steps before invoking the view layer to render a template or returning a JSON response.
 
-Phoenix controllers also build on the Plug package, and are themselves plugs. Controllers provide the functions to do almost anything we need to in an action. If we do find ourselves looking for something that Phoenix controllers don't provide, we might find what we're looking for in Plug itself. Please see the [Plug Guide](plug.html) or [Plug Documentation](https://hexdocs.pm/plug/) for more information.
+Phoenix controllers also build on the Plug package, and are themselves plugs. Controllers provide the functions to do almost anything we need to in an action. If we do find ourselves looking for something that Phoenix controllers don't provide, we might find what we're looking for in Plug itself. Please see the [Plug guide](plug.html) or the [Plug documentation](https://hexdocs.pm/plug/) for more information.
 
-A newly generated Phoenix app will have a single controller, the `PageController`, which can be found at `lib/hello_web/controllers/page_controller.ex` and looks like this.
+A newly generated Phoenix app will have a single controller named `PageController`, which can be found at `lib/hello_web/controllers/page_controller.ex` which looks like this:
 
 ```elixir
 defmodule HelloWeb.PageController do
@@ -22,51 +22,51 @@ end
 
 The first line below the module definition invokes the `__using__/1` macro of the `HelloWeb` module, which imports some useful modules.
 
-The `PageController` gives us the `index` action to display the Phoenix welcome page associated with the default route Phoenix defines in the router.
+The `PageController` gives us the `index` action to display the Phoenix [welcome page] associated with the default route Phoenix defines in the router.
 
 ## Actions
 
 Controller actions are just functions. We can name them anything we like as long as they follow Elixir's naming rules. The only requirement we must fulfill is that the action name matches a route defined in the router.
 
-For example, in `lib/hello_web/router.ex` we could change the action name in the default route that Phoenix gives us in a new app from index:
+For example, in `lib/hello_web/router.ex` we could change the action name in the default route that Phoenix gives us in a new app from `index`:
 
 ```elixir
 get "/", PageController, :index
 ```
 
-To test:
+to `home`:
 
 ```elixir
-get "/", PageController, :test
+get "/", PageController, :home
 ```
 
-As long as we change the action name in the `PageController` to `test` as well, the welcome page will load as before.
+as long as we change the action name in the `PageController` to `home` as well, the [welcome page] will load as before.
 
 ```elixir
 defmodule HelloWeb.PageController do
   ...
 
-  def test(conn, _params) do
+  def home(conn, _params) do
     render(conn, "index.html")
   end
 end
 ```
 
-While we can name our actions whatever we like, there are conventions for action names which we should follow whenever possible. We went over these in the [Routing Guide](routing.html), but we'll take another quick look here.
+While we can name our actions whatever we like, there are conventions for action names which we should follow whenever possible. We went over these in the [routing guide](routing.html), but we'll take another quick look here.
 
 - index   - renders a list of all items of the given resource type
-- show    - renders an individual item by id
+- show    - renders an individual item by ID
 - new     - renders a form for creating a new item
-- create  - receives params for one new item and saves it in a datastore
-- edit    - retrieves an individual item by id and displays it in a form for editing
-- update  - receives params for one edited item and saves it to a datastore
-- delete  - receives an id for an item to be deleted and deletes it from a datastore
+- create  - receives parameters for one new item and saves it in a data store
+- edit    - retrieves an individual item by ID and displays it in a form for editing
+- update  - receives parameters for one edited item and saves the item to a datas tore
+- delete  - receives an ID for an item to be deleted and deletes it from a data store
 
 Each of these actions takes two parameters, which will be provided by Phoenix behind the scenes.
 
-The first parameter is always `conn`, a struct which holds information about the request such as the host, path elements, port, query string, and much more. `conn` comes to Phoenix via Elixir's Plug middleware framework. More detailed info about `conn` can be found in [Plug's documentation](https://hexdocs.pm/plug/Plug.Conn.html).
+The first parameter is always `conn`, a struct which holds information about the request such as the host, path elements, port, query string, and much more. `conn` comes to Phoenix via Elixir's Plug middleware framework. More detailed information about `conn` can be found in the [Plug.Conn documentation](https://hexdocs.pm/plug/Plug.Conn.html).
 
-The second parameter is `params`. Not surprisingly, this is a map which holds any parameters passed along in the HTTP request. It is a good practice to pattern match against params in the function signature to provide data in a simple package we can pass on to rendering. We saw this in the [Request life-cycle guide](request_lifecycle.html) when we added a messenger parameter to our `show` route in `lib/hello_web/controllers/hello_controller.ex`.
+The second parameter is `params`. Not surprisingly, this is a map which holds any parameters passed along in the HTTP request. It is a good practice to pattern match against parameters in the function signature to provide data in a simple package we can pass on to rendering. We saw this in the [request life-cycle guide](request_lifecycle.html) when we added a messenger parameter to our `show` route in `lib/hello_web/controllers/hello_controller.ex`.
 
 ```elixir
 defmodule HelloWeb.HelloController do
@@ -78,13 +78,13 @@ defmodule HelloWeb.HelloController do
 end
 ```
 
-In some cases - often in `index` actions, for instance - we don't care about parameters because our behavior doesn't depend on them. In those cases, we don't use the incoming params, and simply prepend the variable name with an underscore, `_params`. This will keep the compiler from complaining about the unused variable while still keeping the correct arity.
+In some cases — often in `index` actions, for instance — we don't care about parameters because our behavior doesn't depend on them. In those cases, we don't use the incoming parameters, and simply prefix the variable name with an underscore, calling it `_params`. This will keep the compiler from complaining about the unused variable while still keeping the correct arity.
 
 ## Rendering
 
-Controllers can render content in several ways. The simplest is to render some plain text using the `text/2` function which Phoenix provides.
+Controllers can render content in several ways. The simplest is to render some plain text using the [`text/2`] function which Phoenix provides.
 
-For example, let's rewrite the `show` action from `PageController` to return text instead. For that, we could do the following.
+For example, let's rewrite the `show` action from `HelloController` to return text instead. For that, we could do the following.
 
 ```elixir
 def show(conn, %{"messenger" => messenger}) do
@@ -92,9 +92,9 @@ def show(conn, %{"messenger" => messenger}) do
 end
 ```
 
-Now `/hello/Frank` in your browser should display `From messenger Frank` as plain text without any HTML.
+Now [`/hello/Frank`] in your browser should display `From messenger Frank` as plain text without any HTML.
 
-A step beyond this is rendering pure JSON with the `json/2` function. We need to pass it something that the [Jason library](https://github.com/michalmuskala/jason) can decode into JSON, such as a map. (Jason is one of Phoenix's dependencies.)
+A step beyond this is rendering pure JSON with the [`json/2`] function. We need to pass it something that the [Jason library](https://github.com/michalmuskala/jason) can decode into JSON, such as a map. (Jason is one of Phoenix's dependencies.)
 
 ```elixir
 def show(conn, %{"messenger" => messenger}) do
@@ -102,13 +102,13 @@ def show(conn, %{"messenger" => messenger}) do
 end
 ```
 
-If we again visit `/hello/Frank` in the browser, we should see a block of JSON with the key `id` mapped to the string `"Frank"`.
+If we again visit [`/hello/Frank`] in the browser, we should see a block of JSON with the key `id` mapped to the string `"Frank"`.
 
 ```json
 {"id": "Frank"}
 ```
 
-Phoenix controllers can also render HTML without a view. As you may have already guessed, the `html/2` function does just that. This time, we implement the `show` action like this.
+Phoenix controllers can also render HTML without a view. As you may have already guessed, the [`html/2`](`Phoenix.Controller.html/2`) function does just that. This time, we implement the `show` action like this:
 
 ```elixir
 def show(conn, %{"messenger" => messenger}) do
@@ -125,13 +125,14 @@ def show(conn, %{"messenger" => messenger}) do
 end
 ```
 
-Hitting `/hello/Frank` now renders the HTML string we defined in the `show` action. Note that what we wrote in the action is not an `eex` template. It's a multi-line string, so we interpolate the `messenger` variable like this `#{Plug.HTML.html_escape(messenger)}`, instead of this `<%= messenger %>`.
+Hitting [`/hello/Frank`] now renders the HTML string we defined in the `show` action. Note that what we wrote in the action is not an `EEx` template. It's a multi-line string, so we interpolate the `messenger` variable like this `#{Plug.HTML.html_escape(messenger)}`, instead of this `<%= messenger %>`.
 
-It is worth noting that the `text/2`, `json/2`, and `html/2` functions require neither a Phoenix view, nor a template to render.
+It is worth noting that the [`text/2`], [`json/2`], and [`html/2`] functions require neither a Phoenix view, nor a template to render.
 
-The `json/2` function is obviously useful for writing APIs, and the other two may come in handy, but most of the times we use Phoenix views to build our responses. For this, Phoenix provides the `render/3` function.
+The [`json/2`](`Phoenix.Controller.json/2`) function is obviously useful for writing APIs, and the other two may come in handy, but most of the times we use Phoenix views to build our responses. For this, Phoenix provides the [`render/3`] function.
 
-Let's rollback our `show` action to what we originally wrote in the [Request life-cycle guide](request_lifecycle.html):
+
+Let's rollback our `show` action to what we originally wrote in the [request life-cycle guide](request_lifecycle.html):
 
 ```elixir
 defmodule HelloWeb.HelloController do
@@ -143,9 +144,9 @@ defmodule HelloWeb.HelloController do
 end
 ```
 
-In order for the `render/3` function to work correctly, the controller and view must have the same root name, which must also have the same root name as the template directory where the `show.html.eex` template lives. In other words, the `HelloController` requires `HelloView`, and `HelloView` requires the existence of the `lib/hello_web/templates/hello` directory, which must contain the `show.html.eex` template.
+In order for the [`render/3`] function to work correctly, the controller and view must share the same root name (in this case `Hello`), and it also must have the same root name as the template directory (in this case `hello`) where the `show.html.eex` template lives. In other words, the `HelloController` requires `HelloView`, and `HelloView` requires the existence of the `lib/hello_web/templates/hello` directory, which must contain the `show.html.eex` template.
 
-`render/3` will also pass the value which the `show` action received for `messenger` from the params as an assign.
+[`render/3`] will also pass the value which the `show` action received for `messenger` from the parameters as an assign.
 
 If we need to pass values into the template when using `render`, that's easy. We can pass a keyword like we've seen with `messenger: messenger`, or we can use `Plug.Conn.assign/3`, which conveniently returns `conn`.
 
@@ -159,7 +160,7 @@ If we need to pass values into the template when using `render`, that's easy. We
 
 Note: Using `Phoenix.Controller` imports `Plug.Conn`, so shortening the call to `assign/3` works just fine.
 
-Passing more than one value in to our template is as simple as connecting `assign/3` functions together:
+Passing more than one value to our template is as simple as connecting `assign/3` functions together:
 
 ```elixir
   def show(conn, %{"messenger" => messenger}) do
@@ -170,13 +171,13 @@ Passing more than one value in to our template is as simple as connecting `assig
   end
 ```
 
-Generally speaking, once all assigns are configured, we invoke the View layer. The View layer then renders "show.html" alongside the layout and a response is sent back to the browser.
+Generally speaking, once all assigns are configured, we invoke the view layer. The view layer then renders `show.html` alongside the layout and a response is sent back to the browser.
 
 [Views and templates](views.html) have their own guide, so we won't spend much time on them here. What we will look at is how to assign a different layout, or none at all, from inside a controller action.
 
-### Assigning Layouts
+### Assigning layouts
 
-Layouts are just a special subset of templates. They live in `lib/hello_web/templates/layout`. Phoenix created one for us when we generated our app. The default layout is called `app.html.eex`, and it is the layout into which all templates will be rendered by default.
+Layouts are just a special subset of templates. The live in the `templates/layout` folder (`lib/hello_web/templates/layout`). Phoenix created one for us when we generated our app. The default layout is called `app.html.eex`, and it is the layout into which all templates will be rendered by default.
 
 Since layouts are really just templates, they need a view to render them. This is the `LayoutView` module defined in `lib/hello_web/views/layout_view.ex`. Since Phoenix generated this view for us, we won't have to create a new one as long as we put the layouts we want to render inside the `lib/hello_web/templates/layout` directory.
 
@@ -184,7 +185,7 @@ Before we create a new layout, though, let's do the simplest possible thing and
 
 The `Phoenix.Controller` module provides the `put_layout/2` function for us to switch layouts. This takes `conn` as its first argument and a string for the basename of the layout we want to render. It also accepts `false` to disable the layout altogether.
 
-Edit the `index` action of the `PageController` module `lib/hello_web/controllers/page_controller.ex` to look like this.
+You can edit the `index` action of the `PageController` module in `lib/hello_web/controllers/page_controller.ex` to look like this.
 
 ```elixir
 def index(conn, _params) do
@@ -196,10 +197,18 @@ end
 
 After reloading [http://localhost:4000/](http://localhost:4000/), we should see a very different page, one with no title, logo image, or CSS styling at all.
 
-Now let's actually create another layout and render the index template into it. As an example, let's say we had a different layout for the admin section of our application which didn't have the logo image. To do this, let's copy the existing `app.html.eex` to a new file `admin.html.eex` in the same directory `lib/hello_web/templates/layout`. Then let's remove the line in `admin.html.eex` that displays the logo.
+Now let's actually create another layout and render the index template into it. As an example, let's say we had a different layout for the admin section of our application which didn't have the logo image. To do this, let's copy the existing `app.html.eex` to a new file `admin.html.eex` in the same directory `lib/hello_web/templates/layout`. Then let's replace the lines in `admin.html.eex` that displays the logo with the word "Administration".
+
+Remove these lines:
+```html
+<a href="https://phoenixframework.org/" class="phx-logo">
+  <img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
+</a>
+```
 
+Replace them with:
 ```html
-<span class="logo"></span> <!-- remove this line -->
+<p>Administration</p>
 ```
 
 Then, pass the basename of the new layout into `put_layout/2` in our `index` action in `lib/hello_web/controllers/page_controller.ex`.
@@ -212,15 +221,15 @@ def index(conn, _params) do
 end
 ```
 
-When we load the page, we should be rendering the admin layout without a logo.
+When we load the page, we should be rendering the admin layout without a logo and with the word "Administration".
 
-### Overriding Rendering Formats
+### Overriding rendering formats
 
 Rendering HTML through a template is fine, but what if we need to change the rendering format on the fly? Let's say that sometimes we need HTML, sometimes we need plain text, and sometimes we need JSON. Then what?
 
 Phoenix allows us to change formats on the fly with the `_format` query string parameter. To make this happen, Phoenix requires an appropriately named view and an appropriately named template in the correct directory.
 
-As an example, let's take the `PageController` index action from a newly generated app. Out of the box, this has the right view, `PageView`, the right templates directory, `lib/hello_web/templates/page`, and the right template for rendering HTML, `index.html.eex`.
+As an example, let's take the `PageController` index action from a newly generated app. Out of the box, this has the right view `PageView`, the right templates directory (`lib/hello_web/templates/page`), and the right template for rendering HTML (`index.html.eex`.)
 
 ```elixir
 def index(conn, _params) do
@@ -234,7 +243,7 @@ What it doesn't have is an alternative template for rendering text. Let's add on
 OMG, this is actually some text.
 ```
 
-There are just a few more things we need to do to make this work. We need to tell our router that it should accept the `text` format. We do that by adding `text` to the list of accepted formats in the `:browser` pipeline. Let's open up `lib/hello_web/router.ex` and change the `plug :accepts` to include `text` as well as `html` like this.
+There are just a few more things we need to do to make this work. We need to tell our router that it should accept the `text` format. We do that by adding `text` to the list of accepted formats in the `:browser` pipeline. Let's open up `lib/hello_web/router.ex` and change `plug :accepts` to include `text` as well as `html` like this.
 
 ```elixir
 defmodule HelloWeb.Router do
@@ -308,7 +317,7 @@ For a list of valid content mime-types, please see the [mime.types](https://gith
 
 We can also set the HTTP status code of a response similarly to the way we set the content type. The `Plug.Conn` module, imported into all controllers, has a `put_status/2` function to do this.
 
-`Plug.Conn.put_status/2` takes `conn` as the first parameter and as the second parameter either an integer or a "friendly name" used as an atom for the status code we want to set. The list of status code atom representations can be found in `Plug.Conn.Status.code/1` documentation.
+[`Plug.Conn.put_status/2`](https://hexdocs.pm/plug/Plug.Conn.html#put_status/2) takes `conn` as the first parameter and as the second parameter either an integer or a "friendly name" used as an atom for the status code we want to set. The list of status code atom representations can be found in [`Plug.Conn.Status.code/1`](https://hexdocs.pm/plug/Plug.Conn.Status.html#code/1) documentation.
 
 Let's change the status in our `PageController` `index` action.
 
@@ -324,30 +333,43 @@ The status code we provide must be a valid number.
 
 ## Redirection
 
-Often, we need to redirect to a new url in the middle of a request. A successful `create` action, for instance, will usually redirect to the `show` action for the resource we just created. Alternately, it could redirect to the `index` action to show all the things of that same type. There are plenty of other cases where redirection is useful as well.
+Often, we need to redirect to a new URL in the middle of a request. A successful `create` action, for instance, will usually redirect to the `show` action for the resource we just created. Alternately, it could redirect to the `index` action to show all the things of that same type. There are plenty of other cases where redirection is useful as well.
 
-Whatever the circumstance, Phoenix controllers provide the handy `redirect/2` function to make redirection easy. Phoenix differentiates between redirecting to a path within the application and redirecting to a url - either within our application or external to it.
+Whatever the circumstance, Phoenix controllers provide the handy [`redirect/2`] function to make redirection easy. Phoenix differentiates between redirecting to a path within the application and redirecting to a URL — either within our application or external to it.
 
-In order to try out `redirect/2`, let's create a new route in `lib/hello_web/router.ex`.
+In order to try out [`redirect/2`], let's create a new route in `lib/hello_web/router.ex`.
 
 ```elixir
 defmodule HelloWeb.Router do
-  use HelloWeb, :router
   ...
 
   scope "/", HelloWeb do
     ...
     get "/", PageController, :index
     get "/redirect_test", PageController, :redirect_test
+    ...
+  end
+end
+```
+
+Then we'll change the `PageController` `index` action of our controller to do nothing but to redirect to our new route.
+
+```elixir
+defmodule HelloWeb.PageController do
+  use HelloWeb, :controller
+
+  def index(conn, _params) do
+    redirect(conn, to: "/redirect_test")
   end
 end
+
 ```
 
-Then we'll change the `index` action to do nothing but redirect to our new route.
+Actually, we should make use of the path helpers, which are the preferred approach to link to any page within our application, as we learned about in the [routing guide](routing.html).
 
 ```elixir
 def index(conn, _params) do
-  redirect(conn, to: "/redirect_test")
+  redirect(conn, to: Routes.page_path(conn, :redirect_test))
 end
 ```
 
@@ -359,7 +381,7 @@ def redirect_test(conn, _params) do
 end
 ```
 
-When we reload our [Welcome Page](http://localhost:4000), we see that we've been redirected to `/redirect_test` which shows the original welcome page. It works!
+When we reload our [welcome page], we see that we've been redirected to `/redirect_test` which shows the original welcome page. It works!
 
 If we care to, we can open up our developer tools, click on the network tab, and visit our root route again. We see two main requests for this page - a get to `/` with a status of `302`, and a get to `/redirect_test` with a status of `200`.
 
@@ -371,25 +393,11 @@ def index(conn, _params) do
 end
 ```
 
-We can also make use of the path helpers we learned about in the [Routing Guide](routing.html).
-
-```elixir
-defmodule HelloWeb.PageController do
-  use HelloWeb, :controller
-
-  def index(conn, _params) do
-    redirect(conn, to: Routes.page_path(conn, :redirect_test))
-  end
-end
-```
-
-Using route helpers is actually the preferred approach to link to any page within our application.
-
-## Flash Messages
+## Flash messages
 
 Sometimes we need to communicate with users during the course of an action. Maybe there was an error updating a schema, or maybe we just want to welcome them back to the application. For this, we have flash messages.
 
-The `Phoenix.Controller` module provides the `put_flash/3` and `get_flash/2` functions to help us set and retrieve flash messages as a key value pair. Let's set two flash messages in our `HelloWeb.PageController` to try this out.
+The `Phoenix.Controller` module provides the [`put_flash/3`] and [`get_flash/2`] functions to help us set and retrieve flash messages as a key-value pair. Let's set two flash messages in our `HelloWeb.PageController` to try this out.
 
 To do this we modify the `index` action as follows:
 
@@ -405,16 +413,16 @@ defmodule HelloWeb.PageController do
 end
 ```
 
-In order to see our flash messages, we need to be able to retrieve them and display them in a template/layout. One way to do the first part is with `get_flash/2` which takes `conn` and the key we care about. It then returns the value for that key.
+In order to see our flash messages, we need to be able to retrieve them and display them in a template layout. One way to do the first part is with [`get_flash/2`] which takes `conn` and the key we care about. It then returns the value for that key.
 
-Fortunately, our application layout, `lib/hello_web/templates/layout/app.html.eex`, already has markup for displaying flash messages.
+For our convenience, the application layout, `lib/hello_web/templates/layout/app.html.eex`, already has markup for displaying flash messages.
 
 ```html
 <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
 <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 ```
 
-When we reload the [Welcome Page](http://localhost:4000/), our messages should appear just above "Welcome to Phoenix!"
+When we reload the [welcome page], our messages should appear just above "Welcome to Phoenix!"
 
 The flash functionality is handy when mixed with redirects. Perhaps you want to redirect to a page with some extra information. If we reuse the redirect action from the previous section, we can do:
 
@@ -427,17 +435,17 @@ The flash functionality is handy when mixed with redirects. Perhaps you want to
   end
 ```
 
-Now if you reload the Welcome page, you will be redirected and the flash messages will be shown once more.
+Now if you reload the [welcome page], you will be redirected and the flash messages will be shown once more.
 
-Besides `put_flash/3` and `get_flash/2`, the `Phoenix.Controller` module has another useful function worth knowing about. `clear_flash/1` takes only `conn` and removes any flash messages which might be stored in the session.
+Besides [`put_flash/3`] and [`get_flash/2`], the `Phoenix.Controller` module has another useful function worth knowing about. [`clear_flash/1`] takes only `conn` and removes any flash messages which might be stored in the session.
 
 Phoenix does not enforce which keys are stored in the flash. As long as we are internally consistent, all will be well. `:info` and `:error`, however, are common and are handled by default in our templates.
 
-## Action Fallback
+## Action fallback
 
-Action Fallback allows us to centralize error handling code in plugs which are called when a controller action fails to return a `%Plug.Conn{}` struct. These plugs receive both the `conn` which was originally passed to the controller action along with the return value of the action.
+Action fallback allows us to centralize error handling code in plugs which are called when a controller action fails to return a [`%Plug.Conn{}`](https://hexdocs.pm/plug/Plug.Conn.html#t:t/0) struct. These plugs receive both the `conn` which was originally passed to the controller action along with the return value of the action.
 
-Let's say we have a `show` action which uses `with` to fetch a blog post and then authorize the current user to view that blog post. In this example we might expect `fetch_post/1` to return `{:error, :not_found}` if the post is not found and `authorize_user/3` might return `{:error, :unauthorized}` if the user is unauthorized. We could use the `ErrorView` Phoenix generates for every new application to handle these error paths accordingly:
+Let's say we have a `show` action which uses [`with`](`Kernel.SpecialForms.with/1`) to fetch a blog post and then authorize the current user to view that blog post. In this example we might expect `fetch_post/1` to return `{:error, :not_found}` if the post is not found and `authorize_user/3` might return `{:error, :unauthorized}` if the user is unauthorized. We could use the `ErrorView` module that Phoenix generates for every new application to handle these error paths accordingly:
 
 ```elixir
 defmodule HelloWeb.MyController do
@@ -466,7 +474,7 @@ end
 
 Now imagine you may need to implement similar logic for every controller and action handled by your API. This would result in a lot of repetition.
 
-Instead we can define a Module Plug which knows how to handle these error cases specifically. Since controllers are Module Plugs, let's define our Plug as a controller:
+Instead we can define a module plug which knows how to handle these error cases specifically. Since controllers are module plugs, let's define our plug as a controller:
 
 ```elixir
 defmodule HelloWeb.MyFallbackController do
@@ -505,4 +513,16 @@ defmodule HelloWeb.MyController do
 end
 ```
 
-Whenever the `with` conditions do not match, `HelloWeb.MyFallbackController` will receive the original `conn` as well as the result of the action and respond appropriately.
+Whenever the `with` conditions do not match, `HelloWeb.MyFallbackController` will receive the original `conn` as well as the result of the action and respond accordingly.
+
+
+[welcome page]: http://localhost:4000/
+[`/hello/Frank`]:  http://localhost:4000/hello/Frank
+[`html/2`]: `Phoenix.Controller.html/2`
+[`json/2`]: `Phoenix.Controller.json/2`
+[`redirect/2`]: `Phoenix.Controller.redirect/2`
+[`render/3`]: `Phoenix.Controller.render/3`
+[`text/2`]: `Phoenix.Controller.text/2`
+[`put_flash/3`]: `Phoenix.Controller.put_flash/3`
+[`get_flash/2`]: `Phoenix.Controller.get_flash/2`
+[`clear_flash/1`]: `Phoenix.Controller.clear_flash/1`
diff --git a/guides/ecto.md b/guides/ecto.md
index 7c85e9f08..71846c306 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -518,6 +518,6 @@ $ mix ecto.migrate
 
 ## Other options
 
-While Phoenix uses the [Ecto project](https://hexdocs.pm/ecto) to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](http://www.erlang.org/doc/man/ets.html) and [DETS](http://www.erlang.org/doc/man/dets.html) are key value data stores built into [OTP](http://www.erlang.org/doc/). OTP also provides a relational database called [mnesia](http://www.erlang.org/doc/man/mnesia.html) with its own query language called QLC. Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores.
+While Phoenix uses the [Ecto project](https://hexdocs.pm/ecto) to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](http://www.erlang.org/doc/man/ets.html) and [DETS](http://www.erlang.org/doc/man/dets.html) are key-value data stores built into [OTP](http://www.erlang.org/doc/). OTP also provides a relational database called [mnesia](http://www.erlang.org/doc/man/mnesia.html) with its own query language called QLC. Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores.
 
 The data world is your oyster, but we won't be covering these options in these guides.
diff --git a/guides/telemetry.md b/guides/telemetry.md
index ac26ec81a..c4eaa5ad5 100644
--- a/guides/telemetry.md
+++ b/guides/telemetry.md
@@ -454,7 +454,7 @@ event.
 
 ## Periodic measurements
 
-You might want to periodically measure key values within
+You might want to periodically measure key-value pairs within
 your application. Fortunately the
 [`:telemetry_poller`](http://hexdocs.pm/telemetry_poller)
 package provides a mechanism for custom measurements,
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index c858061ea..a934eac2d 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -238,7 +238,7 @@ defmodule Phoenix.Endpoint do
   Starts endpoint's configuration cache and possibly the servers for
   handling requests.
   """
-  @callback start_link() :: Supervisor.on_start
+  @callback start_link(keyword) :: Supervisor.on_start
 
   @doc """
   Access the endpoint configuration given by key.
@@ -472,6 +472,13 @@ defmodule Phoenix.Endpoint do
 
       @doc """
       Starts the endpoint supervision tree.
+
+      ## Options
+
+        * `:log_access_url` - if the access url should be logged
+          once the endpoint starts
+
+      All other options are merged into the endpoint configuration.
       """
       def start_link(opts \\ []) do
         Phoenix.Endpoint.Supervisor.start_link(@otp_app, __MODULE__, opts)
diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex
index 571e8afad..49e4077bb 100644
--- a/lib/phoenix/endpoint/supervisor.ex
+++ b/lib/phoenix/endpoint/supervisor.ex
@@ -13,7 +13,7 @@ defmodule Phoenix.Endpoint.Supervisor do
     case Supervisor.start_link(__MODULE__, {otp_app, mod, opts}, name: mod) do
       {:ok, _} = ok ->
         warmup(mod)
-        log_access_info(otp_app, mod)
+        log_access_url(otp_app, mod, opts)
         ok
 
       {:error, _} = error ->
@@ -431,8 +431,8 @@ defmodule Phoenix.Endpoint.Supervisor do
     end
   end
 
-  defp log_access_info(otp_app, endpoint) do
-    if Phoenix.Endpoint.server?(otp_app, endpoint) do
+  defp log_access_url(otp_app, endpoint, opts) do
+    if Keyword.get(opts, :log_access_url, true) && Phoenix.Endpoint.server?(otp_app, endpoint) do
       Logger.info("Access #{inspect(endpoint)} at #{endpoint.url()}")
     end
   end
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index e272e16e4..9ac2e5e23 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -359,13 +359,13 @@ defmodule Phoenix.Router do
         rescue
           e in Plug.Conn.WrapperError ->
             measurements = %{duration: System.monotonic_time() - start}
-            metadata = %{conn: conn, kind: :error, reason: e, stacktrace: __STACKTRACE__}
+            metadata = Map.merge(metadata, %{conn: conn, kind: :error, reason: e, stacktrace: __STACKTRACE__})
             :telemetry.execute([:phoenix, :router_dispatch, :exception], measurements, metadata)
             Plug.Conn.WrapperError.reraise(e)
         catch
           kind, reason ->
             measurements = %{duration: System.monotonic_time() - start}
-            metadata = %{conn: conn, kind: kind, reason: reason, stacktrace: __STACKTRACE__}
+            metadata = Map.merge(metadata, %{conn: conn, kind: kind, reason: reason, stacktrace: __STACKTRACE__})
             :telemetry.execute([:phoenix, :router_dispatch, :exception], measurements, metadata)
             Plug.Conn.WrapperError.reraise(piped_conn, kind, reason, __STACKTRACE__)
         end
diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex
index 785d8e4c8..b924048fb 100644
--- a/lib/phoenix/socket.ex
+++ b/lib/phoenix/socket.ex
@@ -303,9 +303,9 @@ defmodule Phoenix.Socket do
   ## USER API
 
   @doc """
-  Adds key value pairs to socket assigns.
+  Adds key-value pairs to socket assigns.
 
-  A single key value pair may be passed, a keyword list or map
+  A single key-value pair may be passed, a keyword list or map
   of assigns may be provided to be merged into existing socket
   assigns.
 


Test patch:
diff --git a/test/phoenix/router/routing_test.exs b/test/phoenix/router/routing_test.exs
index c76564bc6..c5d1b5933 100644
--- a/test/phoenix/router/routing_test.exs
+++ b/test/phoenix/router/routing_test.exs
@@ -21,6 +21,8 @@ defmodule Phoenix.Router.RoutingTest do
     def image(conn, _params), do: text(conn, conn.params["path"] || "show files")
     def move(conn, _params), do: text(conn, "users move")
     def any(conn, _params), do: text(conn, "users any")
+    def raise(_conn, _params), do: raise("boom")
+    def exit(_conn, _params), do: exit(:boom)
   end
 
   defmodule LogLevel do
@@ -41,6 +43,7 @@ defmodule Phoenix.Router.RoutingTest do
     get "/files/:user_name/*path", UserController, :image
     get "/backups/*path", UserController, :image
     get "/static/images/icons/*image", UserController, :image
+    get "/exit", UserController, :exit
 
     trace("/trace", UserController, :trace)
     options "/options", UserController, :options
@@ -51,6 +54,7 @@ defmodule Phoenix.Router.RoutingTest do
     scope log: :info do
       pipe_through :noop
       get "/plug", SomePlug, []
+      get "/users/:id/raise", UserController, :raise
     end
 
     get "/no_log", SomePlug, [], log: false
@@ -244,6 +248,144 @@ defmodule Phoenix.Router.RoutingTest do
     end
   end
 
+  describe "telemetry" do
+    @router_start_event [:phoenix, :router_dispatch, :start]
+    @router_stop_event [:phoenix, :router_dispatch, :stop]
+    @router_exception_event [:phoenix, :router_dispatch, :exception]
+    @router_events [
+      @router_start_event,
+      @router_stop_event,
+      @router_exception_event
+    ]
+
+    setup context do
+      test_pid = self()
+      test_name = context.test
+
+      :telemetry.attach_many(
+        test_name,
+        @router_events,
+        fn event, measures, metadata, config ->
+          send(test_pid, {:telemetry_event, event, {measures, metadata, config}})
+        end,
+        nil
+      )
+    end
+
+    test "phoenix.router_dispatch.start and .stop are emitted on success" do
+      call(Router, :get, "users/123")
+      assert_received {:telemetry_event, @router_start_event, _event}
+      assert_received {:telemetry_event, @router_stop_event, _event}
+      refute_received {:telemetry_event, @router_exception_event, _event}
+    end
+
+    test "phoenix.router_dispatch.start and .exception are emitted on crash" do
+      assert_raise Plug.Conn.WrapperError, ~r/UndefinedFunctionError/, fn ->
+        call(Router, :get, "route_that_crashes")
+      end
+
+      assert_received {:telemetry_event, @router_start_event, _event}
+      assert_received {:telemetry_event, @router_exception_event, _event}
+      refute_received {:telemetry_event, @router_stop_event, _event}
+    end
+
+    test "phoenix.router_dispatch.start and .exception are emitted on exit" do
+      catch_exit(call(Router, :get, "exit"))
+
+      assert_received {:telemetry_event, @router_start_event, _event}
+      assert_received {:telemetry_event, @router_exception_event, _event}
+      refute_received {:telemetry_event, @router_stop_event, _event}
+    end
+
+    test "phoenix.router_dispatch.start has supported measurements and metadata" do
+      call(Router, :get, "users/123")
+
+      assert_received {:telemetry_event, @router_start_event, {measures, meta, _config}}
+      assert is_integer(measures.system_time)
+
+      assert %{
+               access: :user,
+               conn: %Plug.Conn{state: :unset},
+               log: :debug,
+               path_params: %{"id" => "123"},
+               pipe_through: [],
+               plug: Phoenix.Router.RoutingTest.UserController,
+               plug_opts: :show,
+               route: "/users/:id"
+             } = meta
+    end
+
+    test "phoenix.router_dispatch.stop has supported measurements and metadata" do
+      call(Router, :get, "users/123")
+
+      assert_received {:telemetry_event, @router_stop_event, {measures, meta, _config}}
+      assert is_integer(measures.duration)
+
+      assert %{
+               access: :user,
+               conn: %Plug.Conn{state: :sent},
+               log: :debug,
+               path_params: %{"id" => "123"},
+               pipe_through: [],
+               plug: Phoenix.Router.RoutingTest.UserController,
+               plug_opts: :show,
+               route: "/users/:id"
+             } = meta
+    end
+
+    test "phoenix.router_dispatch.exception has supported measurements and metadata on crash" do
+      assert_raise Plug.Conn.WrapperError, "** (RuntimeError) boom", fn ->
+        call(Router, :get, "users/123/raise")
+      end
+
+      assert_received {:telemetry_event, @router_exception_event, {measures, meta, _config}}
+      assert is_integer(measures.duration)
+
+      assert %{
+               conn: %Plug.Conn{state: :unset},
+               kind: :error,
+               log: :info,
+               path_params: %{"id" => "123"},
+               pipe_through: [:noop],
+               plug: Phoenix.Router.RoutingTest.UserController,
+               plug_opts: :raise,
+               reason: %Plug.Conn.WrapperError{
+                 conn: %Plug.Conn{state: :unset},
+                 kind: :error,
+                 reason: %RuntimeError{message: "boom"},
+                 stack: wrapped_stacktrace
+               },
+               route: "/users/:id/raise",
+               stacktrace: stacktrace
+             } = meta
+
+      assert is_list(wrapped_stacktrace) && length(wrapped_stacktrace) > 0
+      assert is_list(stacktrace) && length(stacktrace) > 0
+    end
+
+    test "phoenix.router_dispatch.exception has supported measurements and metadata on exit" do
+      catch_exit(call(Router, :get, "exit"))
+
+      assert_received {:telemetry_event, @router_exception_event, {measures, meta, _config}}
+      assert is_integer(measures.duration)
+
+      assert %{
+               conn: %Plug.Conn{state: :unset},
+               kind: :exit,
+               log: :debug,
+               path_params: %{},
+               pipe_through: [],
+               plug: Phoenix.Router.RoutingTest.UserController,
+               plug_opts: :exit,
+               reason: :boom,
+               route: "/exit",
+               stacktrace: stacktrace
+             } = meta
+
+      assert is_list(stacktrace) && length(stacktrace) > 0
+    end
+  end
+
   test "route_info returns route string, path params, and more" do
     assert Phoenix.Router.route_info(Router, "GET", "foo/bar/baz", nil) == %{
              log: :debug,