From 4347eea7b28ef148e03c123ab1473d411ea3be54 Mon Sep 17 00:00:00 2001 From: Austin Reifsteck Date: Fri, 19 Apr 2024 14:32:42 -0400 Subject: [PATCH] documentation updates --- content/en/docs/languages/erlang/_index.md | 1 + content/en/docs/languages/erlang/exporters.md | 82 ++++++++++++++-- .../docs/languages/erlang/getting-started.md | 98 ++++++++++++++----- .../docs/languages/erlang/instrumentation.md | 9 ++ 4 files changed, 155 insertions(+), 35 deletions(-) diff --git a/content/en/docs/languages/erlang/_index.md b/content/en/docs/languages/erlang/_index.md index 1f3d6fcb7f4f..5435eb39a8c2 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 de6543af9474..8793a3bb5c58 100644 --- a/content/en/docs/languages/erlang/exporters.md +++ b/content/en/docs/languages/erlang/exporters.md @@ -16,10 +16,64 @@ 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 +144,10 @@ 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 +167,25 @@ 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 9569f721d30b..1e6fd1749c8d 100644 --- a/content/en/docs/languages/erlang/getting-started.md +++ b/content/en/docs/languages/erlang/getting-started.md @@ -26,14 +26,15 @@ 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 +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/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. +[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 +61,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 +70,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 +87,31 @@ 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 +121,21 @@ 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 +165,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 +178,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 +234,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 +272,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, diff --git a/content/en/docs/languages/erlang/instrumentation.md b/content/en/docs/languages/erlang/instrumentation.md index 8d815f5daaef..c509df37ea7a 100644 --- a/content/en/docs/languages/erlang/instrumentation.md +++ b/content/en/docs/languages/erlang/instrumentation.md @@ -17,12 +17,18 @@ Add the following dependencies to your project: - `opentelemetry`: contains the SDK that implements the interfaces defined in the API. Without it, all the functions in the API are no-ops. +Optionally, you may choose to include the `open_telemetry_decorator` library. +Note that this library is not officially supported by the opentelemetry project, +but it's handy for wrapping fuctions up in spans. + ```elixir # mix.exs def deps do [ {:opentelemetry, "~> 1.3"}, {:opentelemetry_api, "~> 1.2"}, + # optionally + {:open_telemetry_decorator, "~> 1.4"} ] end ``` @@ -139,6 +145,9 @@ def child_function() do end ``` +If you are using `OpenTelemetryDecorator`, see its [usage docs](https://hexdocs.pm/open_telemetry_decorator/OpenTelemetryDecorator.html) +for examples of how it can be used. + {{% /tab %}} {{< /tabpane >}} ### Spans in Separate Processes