diff --git a/content/en/docs/languages/erlang/_index.md b/content/en/docs/languages/erlang/_index.md index 1f3d6fcb7f4..5435eb39a8c 100644 --- a/content/en/docs/languages/erlang/_index.md +++ b/content/en/docs/languages/erlang/_index.md @@ -13,6 +13,7 @@ cascade: otelExporter: 1.6 otelPhoenix: 1.1 otelCowboy: 0.2 + otelEcto: 1.2 --- {{% docs/languages/index-intro erlang %}} diff --git a/content/en/docs/languages/erlang/exporters.md b/content/en/docs/languages/erlang/exporters.md index de6543af947..23ab55e6b82 100644 --- a/content/en/docs/languages/erlang/exporters.md +++ b/content/en/docs/languages/erlang/exporters.md @@ -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/). @@ -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 %}} @@ -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. diff --git a/content/en/docs/languages/erlang/getting-started.md b/content/en/docs/languages/erlang/getting-started.md index 9569f721d30..266ab3ff6d6 100644 --- a/content/en/docs/languages/erlang/getting-started.md +++ b/content/en/docs/languages/erlang/getting-started.md @@ -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. @@ -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 %}}"}, @@ -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 @@ -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] ] ], @@ -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. @@ -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, @@ -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 ``` @@ -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) @@ -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,