-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Documentation for adding OpenTelemetry to a phoenix application
- Loading branch information
Showing
3 changed files
with
292 additions
and
1 deletion.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
...instrumentation/erlang/getting-started.md → ...entation/erlang/getting-started/_index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
271 changes: 271 additions & 0 deletions
271
content/en/docs/instrumentation/erlang/getting-started/phoenix.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
--- | ||
title: Phoenix | ||
weight: 3 | ||
--- | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters