Skip to content

Commit

Permalink
[Erlang] RollDice-related doc improvements (open-telemetry#4327)
Browse files Browse the repository at this point in the history
  • Loading branch information
apreifsteck committed Jun 10, 2024
1 parent 11df7eb commit 65c06ed
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 36 deletions.
1 change: 1 addition & 0 deletions content/en/docs/languages/erlang/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ cascade:
otelExporter: 1.6
otelPhoenix: 1.1
otelCowboy: 0.2
otelEcto: 1.2
---

{{% docs/languages/index-intro erlang %}}
Expand Down
93 changes: 85 additions & 8 deletions content/en/docs/languages/erlang/exporters.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,71 @@ collector, which can then export Spans to a self-hosted service like Zipkin or
Jaeger, as well as commercial services. For a full list of available exporters,
see the [registry](/ecosystem/registry/?component=exporter).

For testing purposes the `opentelemetry-erlang` repository has a Collector
configuration,
[config/otel-collector-config.yaml](https://github.com/open-telemetry/opentelemetry-erlang/blob/main/config/otel-collector-config.yaml)
that can be used as a starting point. This configuration is used in
## Setting up the Collector

For testing purposes, you can start with the following Collector configuration
at the root of your project:

```yaml
# otel-collector-config.yaml

# OpenTelemetry Collector config that receives OTLP and exports to Jager
receivers:
otlp:
protocols:
grpc:
endpoint: '0.0.0.0:4317'
http:
endpoint: '0.0.0.0:4318'
processors:
batch:
send_batch_size: 1024
timeout: 5s
exporters:
otlp/jaeger:
endpoint: jaeger-all-in-one:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [logging, otlp/jaeger]
```

For a more detailed example, you can view the
[config](https://github.com/open-telemetry/opentelemetry-erlang/blob/main/config/otel-collector-config.yaml)
that `opentelemetry-erlang` uses for testing.

For the purposes of this tutorial, we'll start the Collector as a docker image
along side our app. For this tutorial, we'll continue along with the Dice Roll
example from the [Getting Started](/docs/languages/erlang/getting-started) guide

Add this docker-compose file to the root of your app:

```yaml
# docker-compose.yml
version: '3'
services:
otel:
image: otel/opentelemetry-collector-contrib:0.98.0
command: ['--config=/conf/otel-collector-config.yaml']
ports:
- 4317:4317
- 4318:4318
volumes:
- ./otel-collector-config.yaml:/conf/otel-collector-config.yaml
links:
- jaeger-all-in-one

jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
ports:
- '16686:16686'
```

This configuration is used in
[docker-compose.yml](https://github.com/open-telemetry/opentelemetry-erlang/blob/main/docker-compose.yml)
to start the Collector with receivers for both HTTP and gRPC that then export to
Zipkin also run by [docker-compose](https://docs.docker.com/compose/).
Expand Down Expand Up @@ -90,9 +151,12 @@ end
Finally, the runtime configuration of the `opentelemetry` and
`opentelemetry_exporter` Applications are set to export to the Collector. The
configurations below show the defaults that are used if none are set, which are
the HTTP protocol with endpoint of `localhost` on port `4318`. If using `grpc`
for the `otlp_protocol` the endpoint should be changed to
`http://localhost:4317`.
the HTTP protocol with endpoint of `localhost` on port `4318`. Note:

- If using `grpc` for the `otlp_protocol` the endpoint should be changed to
`http://localhost:4317`.
- If you're using the docker compose file from above, you should replace
`localhost` with `otel`.

{{< tabpane text=true >}} {{% tab Erlang %}}

Expand All @@ -112,14 +176,27 @@ for the `otlp_protocol` the endpoint should be changed to
{{% /tab %}} {{% tab Elixir %}}

```elixir
# config/runtime.exs
# config/config.exs
config :opentelemetry,
resource: %{service: %{name: "roll_dice_app"}},
span_processor: :batch,
traces_exporter: :otlp

config :opentelemetry_exporter,
otlp_protocol: :http_protobuf,
otlp_endpoint: "http://localhost:4318"
# otlp_endpoint: "http://otel:4318" if using docker compose file
```

{{% /tab %}} {{< /tabpane >}}

You can see your traces by running `docker compose up` in one terminal, then
`mix phx.server` in another. After sending some requests through the app, go to
`http://localhost:16686` and select `roll_dice_app` from the Service drop down,
then click "Find Traces".

## Gotchas

Some environments do not allow containers to execute as root users. If you work
in an environment like this, you can add `user: "1001"` as a top-level key/value
to the `otel` service in the `docker-compose.yml` file used in this tutorial.
105 changes: 77 additions & 28 deletions content/en/docs/languages/erlang/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ get set up with everything you need.

### Example Application

The following example uses a basic [Phoenix](https://www.phoenixframework.org/)
web application. For reference, a complete example of the code you will build
can be found here:
[opentelemetry-erlang-contrib/examples/dice_game](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/examples/dice_game).
You can git clone that project or just follow along in your browser.
The following example will take you through creating a basic
[Phoenix](https://www.phoenixframework.org/) web application and instrumenting
it with OpenTelemetry. For reference, a complete example of the code you will
build can be found here:
[opentelemetry-erlang-contrib/examples/roll_dice](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/examples/roll_dice).

Additional examples can be found [here](/docs/languages/erlang/examples/).

### Initial Setup

Run `mix phx.new roll_dice`. Type "y" to install dependencies.

### Dependencies

We'll need a few other dependencies that Phoenix doesn't come with.
Expand All @@ -60,6 +64,7 @@ We'll need a few other dependencies that Phoenix doesn't come with.
# mix.exs
def deps do
[
# other default deps...
{:opentelemetry, "~> {{% param versions.otelSdk %}}"},
{:opentelemetry_api, "~> {{% param versions.otelApi %}}"},
{:opentelemetry_exporter, "~> {{% param versions.otelExporter %}}"},
Expand All @@ -68,11 +73,12 @@ def deps do
{:opentelemetry_cowboy, "~> {{% param versions.otelCowboy %}}"}
# for Bandit
{:opentelemetry_bandit, "~> {{% version-from-registry instrumentation-erlang-bandit %}}"},
{:opentelemetry_ecto, "~> {{% param versions.otelEcto %}}"} # if using ecto
]
end
```

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

```elixir
# application.ex
Expand All @@ -84,27 +90,32 @@ def start(_type, _args) do
# or
OpentelemetryBandit.setup()
OpentelemetryPhoenix.setup(adapter: :bandit)
OpentelemetryEcto.setup([:dice_game, :repo]) # if using ecto
end
```

If you're using ecto, you'll also want to add
`OpentelemetryEcto.setup([:dice_game, :repo])`.
Also, make sure your `endpoint.ex` file contains the following line:

```elixir
# endpoint.ex
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
```

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, the `dice_game` application will be terminated.
terminates, even abnormally, the `roll_dice` application will be terminated.

```elixir
# mix.exs
def project do
[
app: :dice_game,
app: :roll_dice,
version: "0.1.0",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
releases: [
dice_game: [
roll_dice: [
applications: [opentelemetry: :temporary]
]
],
Expand All @@ -114,20 +125,22 @@ def project do
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 then starting the app with
`mix phx.server`.
The last thing you'll need is to configure the exporter. For development, we can
use the stdout exporter to ensure everything is working properly. Configure
OpenTelemetry's `traces_exporter` like so:

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

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

Run `mix phx.server`.

If everything went well, you should be able to visit
[`localhost:4000`](http://localhost:4000) in your browser and see quite a few
lines that look like this in your terminal.
Expand Down Expand Up @@ -157,7 +170,7 @@ fields are.)
'net.sock.peer.addr' => <<"127.0.0.1">>,
'http.route' => <<"/">>,'phoenix.action' => home,
'phoenix.plug' =>
'Elixir.DiceGameWeb.PageController'}},
'Elixir.RollDiceWeb.PageController'}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
Expand All @@ -170,18 +183,54 @@ configure the exporter for your preferred service.
### Rolling The Dice
Now we'll check out the API endpoint that will let us roll the dice and return a
Now we'll create the API endpoint that will let us roll the dice and return a
random number between 1 and 6.
Before we call our API, let's add our first bit of manual instrumentation. In
our `DiceController` we call a private `dice_roll` method that generates our
```elixir
# router.ex
scope "/api", RollDiceWeb do
pipe_through :api
get "/rolldice", DiceController, :roll
end
```
And create a bare `DiceController` without any instrumentation:
```elixir
# lib/roll_dice_web/controllers/dice_controller.ex
defmodule RollDiceWeb.DiceController do
use RollDiceWeb, :controller
def roll(conn, _params) do
send_resp(conn, 200, roll_dice())
end
defp roll_dice do
to_string(Enum.random(1..6))
end
end
```
If you like, call the route to see the result. You'll still see some telemetry
pop up in your terminal. Now it's time to enrich that telemetry by instrumenting
our `roll` function by hand
In our `DiceController` we call a private `dice_roll` method that generates our
random number. This seems like a pretty important operation, so in order to
capture it in our trace we'll need to wrap it in a span.
```elixir
defp dice_roll do
Tracer.with_span("dice_roll") do
to_string(Enum.random(1..6))
defmodule RollDiceWeb.DiceController do
use RollDiceWeb, :controller
require OpenTelemetry.Tracer, as: Tracer
# ...snip
defp roll_dice do
Tracer.with_span("dice_roll") do
to_string(Enum.random(1..6))
end
end
end
```
Expand All @@ -190,7 +239,7 @@ It would also be nice to know what number it generated, so we can extract it as
a local variable and add it as an attribute on the span.
```elixir
defp dice_roll do
defp roll_dice do
Tracer.with_span("dice_roll") do
roll = Enum.random(1..6)
Expand Down Expand Up @@ -228,7 +277,7 @@ get a random number in response, and 3 spans in your console.
'net.transport' => 'IP.TCP',
'http.route' => <<"/api/rolldice">>,
'phoenix.action' => roll,
'phoenix.plug' => 'Elixir.DiceGameWeb.DiceController'}},
'phoenix.plug' => 'Elixir.RollDiceWeb.DiceController'}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
Expand Down

0 comments on commit 65c06ed

Please sign in to comment.