Repository: phoenixframework/phoenix
Pull request #3800 (2020-04-26 09:57:36)

Elaborate a bit more the intro to channels
Channels are introduced in a way that could make people think broadcasts are automatic.

This patch proposes an intro that makes the programming model a bit more clear in my mind.



Patch:
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index 9a20bb9d8..5121bbfb8 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -119,7 +119,7 @@ The Phoenix Static buildpack uses a predefined Node version but to avoid surpris
 
 ```
 # Node version
-node_version=10.19.0
+node_version=10.20.1
 ```
 
 Please refer to the [configuration section](https://github.com/gjaldon/heroku-buildpack-phoenix-static#configuration) for full details. You can make your own custom build script, but for now we will use the [default one provided](https://github.com/gjaldon/heroku-buildpack-phoenix-static/blob/master/compile).
diff --git a/guides/realtime/channels.md b/guides/realtime/channels.md
index 222778381..a6296f1ae 100644
--- a/guides/realtime/channels.md
+++ b/guides/realtime/channels.md
@@ -14,8 +14,12 @@ Some possible use cases include:
 - Notifying a browser that a page's CSS or JavaScript has changed (this is handy in development)
 
 Conceptually, Channels are pretty simple.
-Clients connect and subscribe to one or more topics, whether that's `public_chat` or `updates:user1`.
-Any message sent on a topic, whether from the server or from a client, is sent to all clients subscribed to that topic (including the sender, if it's subscribed), like this:
+
+First, clients connect to the server using some transport, like WebSocket. Once connected, they join one or more topics. For example, to interact with a public chat room clients may join a topic called `public_chat`, and to receive updates from a product with ID 7, they may need to join a topic called `product_updates:7`.
+
+Clients can push messages to the topics they've joined, and can also receive messages from them. The other way around, Channel servers receive messages from their connected clients, and can push messages to them too.
+
+Servers are able to broadcast messages to all clients subscribed to a certain topic. This is illustrated in the following diagram:
 
 ```plaintext
                                                                   +----------------+
@@ -31,6 +35,8 @@ Any message sent on a topic, whether from the server or from a client, is sent t
                                                                   +----------------+
 ```
 
+Broadcasts work even if the application runs on several nodes/computers. That is, if two clients have their socket connected to different application nodes and are subscribed to the same topic `T`, both of them will receive messages broadcasted to `T`. That is possible thanks to an internal PubSub mechanism.
+
 Channels can support any kind of client: a browser, native app, smart watch, embedded device, or anything else that can connect to a network.
 All the client needs is a suitable library; see the [Client Libraries](#client-libraries) section below.
 Each client library communicates using one of the "transports" that Channels understand.
diff --git a/guides/telemetry.md b/guides/telemetry.md
new file mode 100644
index 000000000..4386868d1
--- /dev/null
+++ b/guides/telemetry.md
@@ -0,0 +1,585 @@
+# Telemetry
+
+In this guide, we will show you how to instrument and report
+on `:telemetry` events in your Phoenix application.
+
+> `te·lem·e·try` - the process of recording and transmitting
+the readings of an instrument.
+
+As you follow along with this guide, we will introduce you to
+the core concepts of Telemetry, you will initialize a
+reporter to capture your application's events as they occur,
+and we will guide you through the steps to properly
+instrument your own functions using `:telemetry`. Let's take
+a closer look at how Telemetry works in your application.
+
+## The Telemetry supervisor
+
+Since v1.5, new Phoenix applications are generated with a
+Telemetry supervisor. This module is responsible for
+managing the lifecycle of your Telemetry processes. It also
+defines a `metrics/0` function, which returns a list of
+[`Telemetry.Metrics`](https://hexdocs.pm/telemetry_metrics)
+that you define for your application.
+
+By default, the supervisor also starts
+[`:telemetry_poller`](http://hexdocs.pm/telemetry_poller).
+By simply adding `:telemetry_poller` as a dependency, you
+can receive VM-related events on a specified interval.
+
+If you are coming from an older version of Phoenix, install
+the `:telemetry_metrics` and `:telemetry_poller` packages:
+
+```elixir
+{:telemetry_metrics, "~> 0.4"},
+{:telemetry_poller, "~> 0.4"}
+```
+
+and create your Telemetry supervisor at
+`lib/my_app_web/telemetry.ex`:
+
+```elixir
+# lib/my_app_web/telemetry.ex
+defmodule MyAppWeb.Telemetry do
+  use Supervisor
+  import Telemetry.Metrics
+
+  def start_link(arg) do
+    Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
+  end
+
+  def init(_arg) do
+    children = [
+      {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
+      # Add reporters as children of your supervision tree.
+      # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
+    ]
+
+    Supervisor.init(children, strategy: :one_for_one)
+  end
+
+  def metrics do
+    [
+      # Phoenix Metrics
+      summary("phoenix.endpoint.stop.duration",
+        unit: {:native, :millisecond}
+      ),
+      summary("phoenix.router_dispatch.stop.duration",
+        tags: [:route],
+        unit: {:native, :millisecond}
+      ),
+      # VM Metrics
+      summary("vm.memory.total", unit: {:byte, :kilobyte}),
+      summary("vm.total_run_queue_lengths.total"),
+      summary("vm.total_run_queue_lengths.cpu"),
+      summary("vm.total_run_queue_lengths.io")
+    ]
+  end
+
+  defp periodic_measurements do
+    [
+      # A module, function and arguments to be invoked periodically.
+      # This function must call :telemetry.execute/3 and a metric must be added above.
+      # {MyApp, :count_users, []}
+    ]
+  end
+end
+```
+
+Make sure to replace MyApp by your actual application name.
+
+Then add to your main application's supervision tree
+(usually in `lib/my_app/application.ex`):
+
+```elixir
+children = [
+  MyApp.Repo,
+  MyAppWeb.Telemetry,
+  MyAppWeb.Endpoint,
+  ...
+]
+```
+
+## Telemetry Events
+
+Many Elixir libraries (including Phoenix) are already using
+the `:telemetry` package (http://hexdocs.pm/telemetry) as a
+way to give users more insight into the behavior of their
+applications, by emitting events at key moments in the
+application lifecycle.
+
+A Telemetry event is made up of the following:
+
+  * `name` - A string (e.g. `"my_app.worker.stop"`) or a
+    list of atoms that uniquely identifies the event.
+
+  * `measurements` - A map of atom keys (e.g. `:duration`)
+    and numeric values.
+
+  * `metadata` - A map of key/value pairs that can be used
+    for tagging metrics.
+
+### A Phoenix Example
+
+Here is an example of an event from your endpoint:
+
+* `[:phoenix, :endpoint, :stop]` - dispatched by
+  `Plug.Telemetry` in your endpoint whenever the response is
+  sent
+
+  * Measurement: `%{duration: native_time}`
+
+  * Metadata: `%{conn: Plug.Conn.t}`
+
+This means that after each request, `Plug`, via `:telemetry`,
+will emit a "stop" event, with a measurement of how long it
+took to get the response:
+
+```elixir
+:telemetry.execute([:phoenix, :endpoint, :stop], %{duration: duration}, %{conn: conn})
+```
+
+## Metrics
+
+> Metrics are aggregations of Telemetry events with a
+> specific name, providing a view of the system's behaviour
+> over time.
+>
+> &#x2015; `Telemetry.Metrics`
+
+The Telemetry.Metrics package provides a common interface
+for defining metrics. What this means is that the package
+does not perform any aggregation of the measurements itself,
+but instead relies on reporters to do so. We will discuss
+reporters in the next section.
+
+Using `Telemetry.Metrics`, you can define a counter metric,
+which counts how many HTTP requests were completed:
+
+```elixir
+Telemetry.Metrics.counter("phoenix.endpoint.stop.duration")
+```
+
+or you could use a distribution metric to see how many
+requests were completed in particular time buckets:
+
+```elixir
+Telemetry.Metrics.distribution("phoenix.endpoint.stop.duration", buckets: [100, 200, 300])
+```
+
+This ability to introspect HTTP requests is really powerful --
+and this is but one of _many_ telemetry events emitted by
+the Phoenix framework! We'll discuss more of these events,
+as well as specific patterns for extracting valuable data
+from Phoenix/Plug events in the
+[Phoenix Metrics](#phoenix-metrics) section later in this
+guide.
+
+> The full list of `:telemetry` events emitted from Phoenix,
+along with their measurements and metadata, is available in
+the "Instrumentation" section of the `Phoenix.Logger` module
+documentation.
+
+### An Ecto Example
+
+Like Phoenix, Ecto ships with built-in telemetry events.
+This means that you can gain introspection into your web
+and database layers using the same tools.
+
+For instance, you might want to graph query execution time:
+
+```elixir
+Telemetry.Metrics.summary("my_app.repo.query.query_time",
+  unit: {:native, :millisecond}
+)
+```
+
+or view how long queries spend queued:
+
+```elixir
+Telemetry.Metrics.distribution("my_app.repo.query.queue_time",
+  unit: {:native, :millisecond},
+  buckets: [10, 50, 100]
+)
+```
+
+> You can learn more about Ecto Telemetry in the "Telemetry
+Events" section of the
+[`Ecto.Repo`](https://hexdocs.pm/ecto/Ecto.Repo.html) module
+documentation.
+
+So far we have seen some of the Telemetry events common to
+Phoenix applications, along with some examples of their
+various measurements and metadata. With all of this data
+just waiting to be consumed, let's talk about reporters.
+
+## Reporters
+
+Reporters subscribe to Telemetry events using the common
+interface provided by `Telemetry.Metrics`. They then
+aggregate the measurements (data) into metrics to provide
+meaningful information about your application.
+
+### Phoenix.LiveDashboard
+
+For developers interested in real-time visualizations for
+their Telemetry metrics, you may be interested in installing
+[`LiveDashboard`](https://hexdocs.pm/phoenix_live_dashboard).
+LiveDashboard acts as a Telemetry.Metrics reporter to render
+your data as beautiful, real-time charts on the dashboard.
+
+### Telemetry.Metrics.ConsoleReporter
+
+`Telemetry.Metrics` ships with a `ConsoleReporter` that can
+be used to print events and metrics to the terminal. You can
+use this reporter to experiment with the metrics discussed in
+this guide.
+
+Uncomment or add the following to this list of children in
+your Telemetry supervision tree (usually in
+`lib/my_app_web/telemetry.ex`):
+
+```elixir
+{Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
+```
+
+> There are numerous reporters available, for services like
+> StatsD, Prometheus, and more. You can find them by
+> searching for "telemetry_metrics" on [hex.pm](https://hex.pm/packages?search=telemetry_metrics).
+
+## Phoenix Metrics
+
+Earlier we looked at the "stop" event emitted by
+`Plug.Telemetry`, and used it to count the number of HTTP
+requests. In reality, it's only somewhat helpful to be
+able to see just the total number of requests. What if you
+wanted to see the number of requests per route, or per route
+_and_ method?
+
+Let's take a look at another event emitted during the HTTP
+request lifecycle, this time from `Phoenix.Router`:
+
+* `[:phoenix, :router_dispatch, :stop]` - dispatched by
+  Phoenix.Router after successfully dispatching to a matched
+  route
+
+  * Measurement: `%{duration: native_time}`
+
+  * Metadata: `%{conn: Plug.Conn.t, route: binary, plug: module, plug_opts: term, path_params: map, pipe_through: [atom]}`
+
+Let's start by grouping these events by route. Add the
+following (if it does not already exist) to the `metrics/0`
+function of your Telemetry supervisor (usually in
+`lib/my_app_web/telemetry.ex`):
+
+```elixir
+# lib/my_app_web/telemetry.ex
+def metrics do
+  [
+    ...metrics...
+    summary("phoenix.router_dispatch.stop.duration",
+      tags: [:route],
+      unit: {:native, :millisecond}
+    )
+  ]
+end
+```
+
+Restart your server, and then make requests to a page or two.
+In your terminal, you should see the ConsoleReporter print
+logs for the Telemetry events it received as a result of
+the metrics definitions you provided.
+
+The log line for each request contains the specific route
+for that request. This is due to specifying the `:tags`
+option for the summary metric, which takes care of our first
+requirement; we can use `:tags` to group metrics by route.
+Note that reporters will necessarily handle tags differently
+depending on the underlying service in use.
+
+Looking more closely at the Router "stop" event, you can see
+that the `Plug.Conn` struct representing the request is
+present in the metadata, but how do you access the
+properties in `conn`?
+
+Fortunately, `Telemetry.Metrics` provides the following
+options to help you classify your events:
+
+* `:tags` - A list of metadata keys for grouping;
+
+* `:tag_values` - A function which transforms the metadata
+  into the desired shape; Note that this function is called
+  for each event, so it's important to keep it fast if the
+  rate of events is high.
+
+> Learn about all the available metrics options in the
+`Telemetry.Metrics` module documentation.
+
+Let's find out how to extract more tags from events that
+include a `conn` in their metadata.
+
+### Extracting tag values from Plug.Conn
+
+Let's add another metric for the route event, this time to
+group by route and method:
+
+```elixir
+summary("phoenix.router_dispatch.stop.duration",
+  tags: [:method, :route],
+  tag_values: &get_and_put_http_method/1,
+  unit: {:native, :millisecond}
+)
+```
+
+We've introduced the `:tag_values` option here, because we
+need to perform a transformation on the event metadata in
+order to get to the values we need.
+
+Add the following private function to your Telemetry module
+to lift the `:method` value from the `Plug.Conn` struct:
+
+```elixir
+# lib/my_app_web/telemetry.ex
+defp get_and_put_http_method(%{conn: %{method: method}} = metadata) do
+  Map.put(metadata, :method, method)
+end
+```
+
+Restart your server and make some more requests. You should
+begin to see logs with tags for both the HTTP method and the
+route.
+
+Note the `:tags` and `:tag_values` options can be applied to
+all `Telemetry.Metrics` types.
+
+## Periodic measurements
+
+You might want to periodically measure key values within
+your application. Fortunately the
+[`:telemetry_poller`](http://hexdocs.pm/telemetry_poller)
+package provides a mechanism for custom measurements,
+which is useful for retrieving process information or for
+performing custom measurements periodically.
+
+Add the following to the list in your Telemetry supervisor's
+`periodic_measurements/0` function, which is a private
+function that returns a list of measurements to take on a
+specified interval.
+
+```elixir
+# lib/my_app_web/telemetry.ex
+defp periodic_measurements do
+  [
+    {MyApp, :measure_users, []},
+    {:process_info,
+      event: [:my_app, :my_server],
+      name: MyApp.MyServer,
+      keys: [:message_queue_len, :memory]}
+  ]
+end
+```
+
+where `MyApp.measure_users/0` could be written like this:
+
+```elixir
+# lib/my_app.ex
+defmodule MyApp do
+  def measure_users do
+    :telemetry.execute([:my_app, :users], %{total: MyApp.users_count()}, %{})
+  end
+end
+```
+
+Now with measurements in place, you can define the metrics for the
+events above:
+
+```elixir
+# lib/my_app_web/telemetry.ex
+def metrics do
+  [
+    ...metrics...
+    # MyApp Metrics
+    last_value("my_app.users.total"),
+    last_value("my_app.my_server.memory", unit: :byte),
+    last_value("my_app.my_server.message_queue_len")
+    summary("my_app.my_server.call.stop.duration"),
+    counter("my_app.my_server.call.exception")
+  ]
+end
+```
+
+> You will implement MyApp.MyServer in the
+[Custom Events](#custom-events) section.
+
+## Libraries using Telemetry
+
+Telemetry is quickly becoming the de-facto standard for
+package instrumentation in Elixir. Here is a list of
+libraries currently emitting `:telemetry` events.
+
+Library authors are actively encouraged to send a PR adding
+their own (in alphabetical order, please):
+
+* [Absinthe](https://hexdocs.pm/absinthe) - Coming Soon!
+* [Broadway](https://hexdocs.pm/broadway) - [Events](https://hexdocs.pm/broadway/Broadway.html#module-telemetry)
+* [Ecto](https://hexdocs.pm/ecto) - [Events](https://hexdocs.pm/ecto/Ecto.Repo.html#module-telemetry-events)
+* [Oban](https://hexdocs.pm/oban) - [Events](https://hexdocs.pm/oban/Oban.Telemetry.html)
+* [Phoenix](https://hexdocs.pm/phoenix) - [Events](https://hexdocs.pm/phoenix/Phoenix.Logger.html#module-instrumentation)
+* [Plug](https://hexdocs.pm/plug) - [Events](https://hexdocs.pm/plug/Plug.Telemetry.html)
+* [Tesla](https://hexdocs.pm/tesla) - [Events](https://hexdocs.pm/tesla/Tesla.Middleware.Telemetry.html)
+
+## Custom Events
+
+If you need custom metrics and instrumentation in your
+application, you can utilize the `:telemetry` package
+(https://hexdocs.pm/telemetry) just like your favorite
+frameworks and libraries.
+
+Here is an example of a simple GenServer that emits telemetry
+events. Create this file in your app at
+`lib/my_app/my_server.ex`:
+
+```elixir
+# lib/my_app/my_server.ex
+defmodule MyApp.MyServer do
+  @moduledoc """
+  An example GenServer that runs arbitrary functions and emits telemetry events when called.
+  """
+  use GenServer
+
+  # A common prefix for :telemetry events
+  @prefix [:my_app, :my_server, :call]
+
+  def start_link(fun) do
+    GenServer.start_link(__MODULE__, fun, name: __MODULE__)
+  end
+
+  @doc """
+  Runs the function contained within this server.
+
+  ## Events
+
+  The following events may be emitted:
+
+    * `[:my_app, :my_server, :call, :start]` - Dispatched
+      immediately before invoking the function. This event
+      is always emitted.
+
+      * Measurement: `%{system_time: system_time}`
+
+      * Metadata: `%{}`
+
+    * `[:my_app, :my_server, :call, :stop]` - Dispatched
+      immediately after successfully invoking the function.
+
+      * Measurement: `%{duration: native_time}`
+
+      * Metadata: `%{}`
+
+    * `[:my_app, :my_server, :call, :exception]` - Dispatched
+      immediately after invoking the function, in the event
+      the function throws or raises.
+
+      * Measurement: `%{duration: native_time}`
+
+      * Metadata: `%{kind: kind, reason: reason, stacktrace: stacktrace}`
+  """
+  def call!, do: GenServer.call(__MODULE__, :called)
+
+  @impl true
+  def init(fun) when is_function(fun, 0), do: {:ok, fun}
+
+  @impl true
+  def handle_call(:called, _from, fun) do
+    # Wrap the function invocation in a "span"
+    result = telemetry_span(fun)
+
+    {:reply, result, fun}
+  end
+
+  # Emits telemetry events related to invoking the function
+  defp telemetry_span(fun) do
+    start_time = emit_start()
+
+    try do
+      fun.()
+    catch
+      kind, reason ->
+        stacktrace = System.stacktrace()
+        duration = System.monotonic_time() - start_time
+        emit_exception(duration, kind, reason, stacktrace)
+        :erlang.raise(kind, reason, stacktrace)
+    else
+      result ->
+        duration = System.monotonic_time() - start_time
+        emit_stop(duration)
+        result
+    end
+  end
+
+  defp emit_start do
+    start_time_mono = System.monotonic_time()
+
+    :telemetry.execute(
+      @prefix ++ [:start],
+      %{system_time: System.system_time()},
+      %{}
+    )
+
+    start_time_mono
+  end
+
+  defp emit_stop(duration) do
+    :telemetry.execute(
+      @prefix ++ [:stop],
+      %{duration: duration},
+      %{}
+    )
+  end
+
+  defp emit_exception(duration, kind, reason, stacktrace) do
+    :telemetry.execute(
+      @prefix ++ [:exception],
+      %{duration: duration},
+      %{
+        kind: kind,
+        reason: reason,
+        stacktrace: stacktrace
+      }
+    )
+  end
+end
+```
+
+and add it to your application's supervisor tree (usually in
+`lib/my_app/application.ex`), giving it a function to invoke
+when called:
+
+```elixir
+# lib/my_app/application.ex
+children = [
+  # Start a server that greets the world
+  {MyApp.MyServer, fn -> "Hello, world!" end},
+]
+```
+
+Now start an IEx session and call the server:
+
+```elixir
+iex(1)> MyApp.MyServer.call!
+```
+
+and you should see something like the following output:
+
+```elixir
+[Telemetry.Metrics.ConsoleReporter] Got new event!
+Event name: my_app.my_server.call.stop
+All measurements: %{duration: 4000}
+All metadata: %{}
+
+Metric measurement: #Function<2.111777250/1 in Telemetry.Metrics.maybe_convert_measurement/2> (summary)
+With value: 0.004 millisecond
+Tag values: %{}
+
+"Hello, world!"
+```
diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs
index 3618e1aea..7bc051dbe 100644
--- a/installer/templates/phx_single/mix.exs
+++ b/installer/templates/phx_single/mix.exs
@@ -65,7 +65,7 @@ defmodule <%= app_module %>.MixProject do
       setup: ["deps.get"<%= if ecto do %>, "ecto.setup"<% end %><%= if webpack do %>, "cmd npm install --prefix assets"<% end %>]<%= if ecto do %>,
       "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
       "ecto.reset": ["ecto.drop", "ecto.setup"],
-      test: ["ecto.create --quiet", "ecto.migrate", "test"]<% end %>
+      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]<% end %>
     ]
   end
 end
diff --git a/installer/templates/phx_umbrella/apps/app_name/mix.exs b/installer/templates/phx_umbrella/apps/app_name/mix.exs
index eca153a08..3957bd1c8 100644
--- a/installer/templates/phx_umbrella/apps/app_name/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name/mix.exs
@@ -51,7 +51,7 @@ defmodule <%= app_module %>.MixProject do
       setup: ["deps.get"<%= if ecto do %>, "ecto.setup"<% end %>]<%= if ecto do %>,
       "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
       "ecto.reset": ["ecto.drop", "ecto.setup"],
-      test: ["ecto.create --quiet", "ecto.migrate", "test"]<% end %>
+      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]<% end %>
     ]
   end
 end
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
index e184f08ec..2ea4511af 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
@@ -59,7 +59,7 @@ defmodule <%= web_namespace %>.MixProject do
   defp aliases do
     [
       setup: ["deps.get"<%= if webpack do %>, "cmd npm install --prefix assets"<% end %>]<%= if ecto do %>,
-      test: ["ecto.create --quiet", "ecto.migrate", "test"]<% end %>
+      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]<% end %>
     ]
   end
 end
diff --git a/lib/mix/tasks/phx.gen.secret.ex b/lib/mix/tasks/phx.gen.secret.ex
index 2bbe426d3..277ff088e 100644
--- a/lib/mix/tasks/phx.gen.secret.ex
+++ b/lib/mix/tasks/phx.gen.secret.ex
@@ -25,7 +25,7 @@ defmodule Mix.Tasks.Phx.Gen.Secret do
   end
 
   defp random_string(length) when length > 31 do
-    :crypto.strong_rand_bytes(length) |> Base.encode64 |> binary_part(0, length)
+    :crypto.strong_rand_bytes(length) |> Base.encode64(padding: false) |> binary_part(0, length)
   end
   defp random_string(_), do: Mix.raise "The secret should be at least 32 characters long"
 
diff --git a/mix.exs b/mix.exs
index 4fdd6f9d2..d6ab4ee7a 100644
--- a/mix.exs
+++ b/mix.exs
@@ -79,7 +79,7 @@ defmodule Phoenix.MixProject do
       # Test dependencies
       {:gettext, "~> 0.15.0", only: :test},
       {:telemetry_poller, "~> 0.4", only: :test},
-      {:telemetry_metrics, "~> 0.4", only: :test},
+      {:telemetry_metrics, "~> 0.4", only: [:docs, :test]},
       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
     ]
   end
@@ -123,6 +123,7 @@ defmodule Phoenix.MixProject do
       "guides/ecto.md",
       "guides/contexts.md",
       "guides/mix_tasks.md",
+      "guides/telemetry.md",
       "guides/realtime/channels.md",
       "guides/realtime/presence.md",
       "guides/testing/testing.md",


Test patch:
diff --git a/test/mix/tasks/phx.gen.secret_test.exs b/test/mix/tasks/phx.gen.secret_test.exs
index 8dab8f984..5f12585fa 100644
--- a/test/mix/tasks/phx.gen.secret_test.exs
+++ b/test/mix/tasks/phx.gen.secret_test.exs
@@ -7,11 +7,13 @@ defmodule Mix.Tasks.Phx.Gen.SecretTest do
   test "generates a secret" do
     run []
     assert_receive {:mix_shell, :info, [secret]} when byte_size(secret) == 64
+    assert String.printable?(secret)
   end
 
   test "generates a secret with custom length" do
     run ["32"]
     assert_receive {:mix_shell, :info, [secret]} when byte_size(secret) == 32
+    assert String.printable?(secret)
   end
 
   test "raises on invalid args" do