Skip to content

Commit

Permalink
Documentation for adding OpenTelemetry to a phoenix application
Browse files Browse the repository at this point in the history
  • Loading branch information
marcdel committed May 12, 2023
1 parent cd4c323 commit 835c5ca
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Getting Started
weight: 20
weight: 1
---

Welcome to the OpenTelemetry for Erlang/Elixir getting started guide! This guide
Expand Down
271 changes: 271 additions & 0 deletions content/en/docs/instrumentation/erlang/getting-started/phoenix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
---
title: Phoenix
weight: 2
---

This guide will show you how to get started with OpenTelemetry in the Phoenix
Web Framework.

You will learn how you can instrument a simple application automatically, in
such a way that traces are emitted to the console.

## Prerequisites

Ensure that you have erlang, elixir, postgres (or the database of your choice),
and phoenix installed locally. The phoenix
[installation guide](https://hexdocs.pm/phoenix/installation.html) will help you
get set up with everything you need.

## Example Application

The following example uses a basic [Phoenix](https://www.phoenixframework.org/)
web application. For more elaborate examples, see
[examples](/docs/instrumentation/erlang/examples/).

To begin, use the `phx` generator to create a new project

```shell
mix phx.new your_app_name
```

Press `n` when asked to fetch and install dependencies, we'll do that after the
next step.

ℹ️ Throughout this guide I will use `your_app_name` or `YourAppName`, be sure to
replace these with your actual application's name.

### Dependencies

We'll need a few other dependencies that Phoenix doesn't come with as well.

- `opentelemetry_api`: contains the interfaces you'll use to instrument your
code. Things like `Tracer.with_span` and `Tracer.set_attribute` are defined
here.
- `opentelemetry`: contains the SDK that implements the interfaces defined in
the API. Without it, all the functions in the API are no-ops.
- `opentelemetry_exporter`: allows you to send your telemetry data to an
OpenTelemetry Collector and/or to self-hosted or commercial services.
- `opentelemetry_ecto`: creates OpenTelemetry spans from the Elixir `:telemetry`
events created by Ecto.
- `opentelemetry_phoenix`: creates OpenTelemetry spans from the Elixir
`:telemetry` events created by Phoenix.
- `opentelemetry_cowboy`: creates OpenTelemetry spans from the Elixir
`:telemetry` events created by the Cowboy web server (which is used by
Phoenix).

```elixir
# mix.exs
def deps do
[
{:opentelemetry, "~> 1.3"},
{:opentelemetry_api, "~> 1.2"},
{:opentelemetry_exporter, "~> 1.4"},
{:opentelemetry_ecto, "~> 1.1"},
{:opentelemetry_phoenix, "~> 1.1"},
{:opentelemetry_cowboy, "~> 0.2"}
]
end
```

The last three also need to be setup when your application starts:

```elixir
# application.ex
@impl true
def start(_type, _args) do
:opentelemetry_cowboy.setup()
OpentelemetryPhoenix.setup()
OpentelemetryEcto.setup([:your_app_name, :repo])
end
```

We also need to configure the `opentelemetry` application as temporary by adding
a `releases` section to your project configuration. This will ensure that if it
terminates, even abnormally, no other applications will be terminated.

```elixir
# mix.exs
def project do
[
app: :your_app_name,
version: "0.1.0",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
releases: [
your_app_name: [
applications: [opentelemetry: :temporary]
]
],
aliases: aliases(),
deps: deps()
]
end
```

Now we can use the new `mix setup` command to install the dependencies, build
the assets, and create and migrate the database.

### Try It Out!

We can ensure everything is working by setting the stdout exporter as
opentelemetry's traces_exporter and starting the app with `mix phx.server`.

```elixir
# config/dev.exs
config :opentelemetry, traces_exporter: {:otel_exporter_stdout, []}
```

If everything went well, you should be able to visit `http://localhost:4000` in
your browser and see quite a few lines that look like this in your terminal.
Don't worry if the format looks a little unfamiliar. Spans are recorded in the
Erlang `record` data structure. You can find more information about records
[here](https://www.erlang.org/doc/reference_manual/records.html), and
[this](https://github.com/open-telemetry/opentelemetry-erlang/blob/main/apps/opentelemetry/include/otel_span.hrl#L19)
file describes the `span` record structure, and explains what all the different
fields are.

```shell
*SPANS FOR DEBUG*
{span,64480120921600870463539706779905870846,11592009751350035697,[],
undefined,<<"/">>,server,-576460731933544855,-576460731890088522,
{attributes,128,infinity,0,
#{'http.status_code' => 200,
'http.client_ip' => <<"127.0.0.1">>,
'http.flavor' => '1.1','http.method' => <<"GET">>,
'http.scheme' => <<"http">>,'http.target' => <<"/">>,
'http.user_agent' =>
<<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36">>,
'net.transport' => 'IP.TCP',
'net.host.name' => <<"localhost">>,
'net.host.port' => 4000,'net.peer.port' => 62839,
'net.sock.host.addr' => <<"127.0.0.1">>,
'net.sock.peer.addr' => <<"127.0.0.1">>,
'http.route' => <<"/">>,'phoenix.action' => home,
'phoenix.plug' =>
'Elixir.OtelPhxSetupWeb.PageController'}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"opentelemetry_phoenix">>,<<"1.1.0">>,
undefined}}
```
These are the raw structured logs that will get sent when you configure the
exporter for your preferred service.
### Rolling The Dice
Now we'll add an API endpoint that will let us roll the dice and return a random
number between 1 and 6.
To start, we'll add a route to the `/api` scope in your router
```elixir
# lib/your_app_name_web/router.ex
scope "/api", OtelPhxSetupWeb do
pipe_through :api
get "/rolldice", DiceController, :roll
end
```
Then we'll create a new file in the controllers folder for that module. We told
the router that we will define a roll function, so we'll do that. It will return
a `200` response code and the result of a `dice_roll` function, which we will
emit a span for. We also want to set the value of the generated roll as an
attribute on the span.
```elixir
# lib/your_app_name_web/controllers/dice_controller.ex
defmodule YourAppNameWeb.DiceController do
use YourAppNameWeb, :controller
require OpenTelemetry.Tracer, as: Tracer
def roll(conn, _params) do
send_resp(conn, 200, dice_roll())
end
defp dice_roll do
Tracer.with_span("dice_roll") do
roll = Enum.random(1..6)
Tracer.set_attribute(:roll, roll)
to_string(roll)
end
end
end
```
If you point your browser/curl/etc. to `http://localhost:4000/api/rolldice` you
should get a random number in response, and a few spans in your console. I'll
point out some of the most relevant bits.
```shell
<<"/api/rolldice">>
'http.status_code' => 200
'http.method' => <<"GET">>
'http.target' => <<"/api/rolldice">>
<<"dice_roll">>
{attributes,128,infinity,0,#{roll => 3}}
```
<details>
<summary>View the full spans</summary>
```shell
{span,179794490720632128348966072237843029671,8335582746889793882,[],
undefined,<<"/api/rolldice">>,server,-576460682893356250,
-576460682892293541,
{attributes,128,infinity,0,
#{'http.status_code' => 200,
'http.client_ip' => <<"127.0.0.1">>,
'http.flavor' => '1.1','http.method' => <<"GET">>,
'http.scheme' => <<"http">>,
'http.target' => <<"/api/rolldice">>,
'http.user_agent' =>
<<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36">>,
'net.transport' => 'IP.TCP',
'net.host.name' => <<"localhost">>,
'net.host.port' => 4000,'net.peer.port' => 65111,
'net.sock.host.addr' => <<"127.0.0.1">>,
'net.sock.peer.addr' => <<"127.0.0.1">>,
'http.route' => <<"/api/rolldice">>,
'phoenix.action' => roll,
'phoenix.plug' =>
'Elixir.OtelPhxSetupWeb.DiceController'}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"opentelemetry_phoenix">>,<<"1.1.0">>,
undefined}}
{span,179794490720632128348966072237843029671,4318919905237729135,[],
8335582746889793882,<<"dice_roll">>,internal,-576460682892357583,
-576460682892320375,
{attributes,128,infinity,0,#{roll => 3}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"otel_phx_setup">>,<<"0.1.0">>,undefined}}
```
</details>
<br/>
You will have other spans from Phoenix, Cowboy, and Ecto as well, and these will
all be linked together into a trace which can be rendered into a nice waterfall
diagram in your tracing tool of choice.
## Next Steps
Enrich your automatically generated instrumentation with
[manual instrumentation](/docs/instrumentation/erlang/instrumentation) of your
own codebase. This allows you to customize the observability data your
application emits.
You'll also want to configure an appropriate exporter to
[export your telemetry data](/docs/instrumentation/erlang/getting-started#exporting-to-the-opentelemetry-collector)
to one or more telemetry backends.
20 changes: 20 additions & 0 deletions static/refcache.json
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,10 @@
"StatusCode": 206,
"LastSeen": "2023-03-15T09:16:58.309342+01:00"
},
"https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html": {
"StatusCode": 206,
"LastSeen": "2023-05-10T22:18:17.077575-07:00"
},
"https://en.cppreference.com/w/cpp/container/set": {
"StatusCode": 200,
"LastSeen": "2023-02-15T21:19:49.382146-05:00"
Expand Down Expand Up @@ -2915,6 +2919,14 @@
"StatusCode": 206,
"LastSeen": "2023-02-15T21:47:29.620967-05:00"
},
"https://hexdocs.pm/phoenix/installation.html": {
"StatusCode": 206,
"LastSeen": "2023-05-10T22:18:17.539786-07:00"
},
"https://hexdocs.pm/phoenix/up_and_running.html": {
"StatusCode": 206,
"LastSeen": "2023-05-10T22:18:17.312938-07:00"
},
"https://httpd.apache.org/docs/2.4/mod/core.html#servername": {
"StatusCode": 206,
"LastSeen": "2023-02-15T21:48:08.863988-05:00"
Expand Down Expand Up @@ -3607,6 +3619,10 @@
"StatusCode": 206,
"LastSeen": "2023-02-18T12:42:09.238995-05:00"
},
"https://rebar3.org/docs/getting-started/": {
"StatusCode": 206,
"LastSeen": "2023-05-10T22:18:16.890082-07:00"
},
"https://redis.io/commands/hmset": {
"StatusCode": 206,
"LastSeen": "2023-02-18T12:42:22.773353-05:00"
Expand Down Expand Up @@ -4087,6 +4103,10 @@
"StatusCode": 206,
"LastSeen": "2023-04-07T13:36:35.670878-04:00"
},
"https://www.erlang.org/doc/reference_manual/records.html": {
"StatusCode": 206,
"LastSeen": "2023-05-12T12:27:37.777863-07:00"
},
"https://www.eventbrite.com/e/otel-unplugged-kubeconcloudnativecon-detroit-2022-tickets-427595037267": {
"StatusCode": 200,
"LastSeen": "2023-02-18T13:43:51.052572-05:00"
Expand Down

0 comments on commit 835c5ca

Please sign in to comment.