Початок роботи
Ласкаво просимо до посібника з початку роботи з OpenTelemetry для Erlang/Elixir! Цей посібник проведе вас через основні кроки встановлення, налаштування та експорту даних з OpenTelemetry.
Phoenix
Ця частина посібника покаже вам, як почати роботу з OpenTelemetry у веб-фреймворку Phoenix.
Передумови
Переконайтеся, що у вас встановлені Erlang, Elixir, PostgreSQL (або база даних на ваш вибір) та Phoenix локально. Посібник з встановлення Phoenix допоможе вам налаштувати все необхідне.
Приклад застосунку
Наступний приклад проведе вас через створення базового вебзастосунку Phoenix та його інструментування за допомогою OpenTelemetry. Для довідки, повний приклад коду, який ви створите, можна знайти тут: opentelemetry-erlang-contrib/examples/roll_dice.
Додаткові приклади можна знайти в opentelemetry-erlang-contrib examples.
Початкове налаштування
Запустіть mix phx.new roll_dice. Введіть “y”, щоб встановити залежності.
Залежності
Нам знадобиться кілька інших залежностей, які не входять до складу Phoenix.
opentelemetry_api: містить інтерфейси, які ви будете використовувати для інструментування вашого коду. Такі речі, якTracer.with_spanтаTracer.set_attribute, визначені тут.opentelemetry: містить SDK, який реалізує інтерфейси, визначені в API. Без нього всі функції в API є no-ops.opentelemetry_exporter: дозволяє надсилати ваші телеметричні дані до OpenTelemetry Collector та/або до самостійно розміщених або комерційних сервісів.opentelemetry_phoenix: створює OpenTelemetry відрізки з подій Elixir:telemetry, створених Phoenix.opentelemetry_cowboy: створює OpenTelemetry відрізки з подій Elixir:telemetry, створених вебсервером Cowboy, який використовується Phoenix.
# mix.exs
def deps do
[
# other default deps...
{:opentelemetry_exporter, "~> 1.8"},
{:opentelemetry, "~> 1.5"},
{:opentelemetry_api, "~> 1.4"},
{:opentelemetry_phoenix, "~> 2"},
{:opentelemetry_cowboy, "~> 1"},
{:opentelemetry_ecto, "~> 1.2"} # якщо використовуєте ecto
]
end
Останні два також потрібно налаштувати при запуску вашого застосунку:
# application.ex
@impl true
def start(_type, _args) do
:opentelemetry_cowboy.setup()
OpentelemetryPhoenix.setup(adapter: :cowboy2)
OpentelemetryEcto.setup([:dice_game, :repo]) # якщо використовуєте ecto
end
Також переконайтеся, що ваш файл endpoint.ex містить наступний рядок:
# endpoint.ex
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
Нам також потрібно налаштувати застосунку opentelemetry як тимчасовий, додавши розділ releases до конфігурації вашого проєкту. Це забезпечить, що якщо він завершиться, навіть аномально, застосунок roll_dice не буде завершено.
# mix.exs
def project do
[
app: :roll_dice,
version: "0.1.0",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
releases: [
roll_dice: [
applications: [opentelemetry: :temporary]
]
],
aliases: aliases(),
deps: deps()
]
end
Останнє, що вам потрібно, це налаштувати експортер. Для розробки ми можемо використовувати stdout експортер, щоб переконатися, що все працює правильно. Налаштуйте traces_exporter OpenTelemetry наступним чином:
# config/dev.exs
config :opentelemetry, traces_exporter: {:otel_exporter_stdout, []}
Тепер ми можемо використовувати нову команду mix setup, щоб встановити залежності, зібрати активи та створити та мігрувати базу даних.
Спробуйте
Запустіть mix phx.server.
Якщо все пройшло добре, ви повинні побачити кілька рядків, схожих на ці, у вашому терміналі, коли відвідаєте localhost:4000 у вашому оглядачі.
(Не хвилюйтеся, якщо формат виглядає трохи незвично. Відрізки записуються в структурі даних Erlang record, а otel_span.hrl описує структуру запису span і пояснює, що таке різні поля.)
{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.RollDiceWeb.PageController'}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"opentelemetry_phoenix">>,<<"1.1.0">>,
undefined}}
Це сирі записи Erlang, які будуть серіалізовані та надіслані, коли ви налаштуєте експортер для вашого улюбленого сервісу.
Кидання кубиків
Тепер ми створимо точку доступу API, який дозволить нам кидати кубики та повертати випадкове число від 1 до 6.
# router.ex
scope "/api", RollDiceWeb do
pipe_through :api
get "/rolldice", DiceController, :roll
end
І створимо простий DiceController без інструментування:
# 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
Якщо хочете, викличте маршрут, щоб побачити результат. Ви все ще побачите деяку телеметрію у вашому терміналі. Тепер настав час збагатити цю телеметрію, інструментуючи нашу функцію roll вручну.
У нашому DiceController ми викликаємо приватний метод dice_roll, який генерує наше випадкове число. Це здається досить важливою операцією, тому для її захоплення у нашому трасуванні нам потрібно обгорнути її у відрізок.
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
Було б також добре знати, яке число було згенеровано, тому ми можемо витягти його як локальну змінну та додати як атрибут до відрізка.
defp roll_dice do
Tracer.with_span("dice_roll") do
roll = Enum.random(1..6)
Tracer.set_attribute(:roll, roll)
to_string(roll)
end
end
Тепер, якщо ви відкриєте у своєму оглядачі/curl/etc. localhost:4000/api/rolldice, ви повинні отримати випадкове число у відповідь та 3 відрізки у вашій консолі.
Переглянути повні відрізки
*SPANS FOR DEBUG*
{span,224439009126930788594246993907621543552,5581431573601075988,[],
undefined,<<"/api/rolldice">>,server,-576460729549928500,
-576460729491912750,
{attributes,128,infinity,0,
#{'http.request_content_length' => 0,
'http.response_content_length' => 1,
'http.status_code' => 200,
'http.client_ip' => <<"127.0.0.1">>,
'http.flavor' => '1.1','http.host' => <<"localhost">>,
'http.host.port' => 4000,'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.host.ip' => <<"127.0.0.1">>,
'net.transport' => 'IP.TCP',
'http.route' => <<"/api/rolldice">>,
'phoenix.action' => roll,
'phoenix.plug' => 'Elixir.RollDiceWeb.DiceController'}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"opentelemetry_cowboy">>,<<"0.2.1">>,
undefined}}
{span,237952789931001653450543952469252891760,13016664705250513820,[],
undefined,<<"HTTP GET">>,server,-576460729422104083,-576460729421433042,
{attributes,128,infinity,0,
#{'http.request_content_length' => 0,
'http.response_content_length' => 1258,
'http.status_code' => 200,
'http.client_ip' => <<"127.0.0.1">>,
'http.flavor' => '1.1','http.host' => <<"localhost">>,
'http.host.port' => 4000,'http.method' => <<"GET">>,
'http.scheme' => <<"http">>,
'http.target' => <<"/favicon.ico">>,
'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.host.ip' => <<"127.0.0.1">>,
'net.transport' => 'IP.TCP'}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"opentelemetry_cowboy">>,<<"0.2.1">>,
undefined}}
{span,224439009126930788594246993907621543552,17387612312604368700,[],
5581431573601075988,<<"dice_roll">>,internal,-576460729494399167,
-576460729494359917,
{attributes,128,infinity,0,#{roll => 2}},
{events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"dice_game">>,<<"0.1.0">>,undefined}}
Створені відрізки
У наведеному вище виведенні є 3 відрізки. Назва відрізка є 6-м елементом у кортежі, і кожен з них детально описаний нижче:
<<"/api/rolldice">>
Це перший відрізок у запиті, тобто кореневий відрізок. Це undefined поруч з імʼям відрізка означає, що він не має батьківського відрізка. Два дуже великі негативні числа — це час початку та завершення відрізка у native одиницях часу. Якщо вам цікаво, ви можете обчислити тривалість у мілісекундах так: System.convert_time_unit(-576460729491912750 - -576460729549928500, :native, :millisecond). phoenix.plug та phoenix.action покажуть вам контролер та функцію, яка обробила запит. Ви також помітите, що instrumentation_scope — це opentelemetry_cowboy. Коли ми сказали функції налаштування opentelemetry_phoenix, що ми хочемо використовувати адаптер :cowboy2, це дало їй знати, що не потрібно створювати додатковий відрізок, а натомість додати атрибути до наявного відрізка cowboy. Це забезпечує більш точні дані про затримку у наших трасуваннях.
<<"HTTP GET">>
Це запит на favicon, який ви можете побачити в атрибуті 'http.target' => <<"/favicon.ico">>. Я вважаю, що він має загальне імʼя, тому що не має http.route.
<<"dice_roll">>
Це користувацький відрізок, який ми додали до нашого приватного методу. Ви помітите, що він має лише один атрибут, який ми встановили, roll => 2. Ви також повинні помітити, що він є частиною тієї ж трасування, що і наш відрізок <<"/api/rolldice">>, 224439009126930788594246993907621543552 і має ідентифікатор батьківського відрізка 5581431573601075988, який є ідентифікатором відрізка <<"/api/rolldice">>. Це означає, що цей відрізок є дочірнім до того, і буде показаний під ним, коли буде відображений у вашому інструменті трасування.
Наступні кроки
Збагачуйте ваше автоматично згенероване інструментування ручним інструментуванням вашого власного коду. Це дозволяє вам налаштовувати дані спостережуваності, які ваш застосунок генерує.
Вам також потрібно буде налаштувати відповідний експортер для експорту ваших даних телеметрії до одного або більше бекендів телеметрії.
Elli
У цьому розділі показано, як розпочати роботу з OpenTelemetry та HTTP-сервером Elli.
Необхідні умови
Переконайтеся, що у вас локально встановлено Erlang та rebar3.
Приклад застосунку
У наведеному нижче прикладі ви дізнаєтеся, як створити базовий веб-застосунок Elli та налаштувати його за допомогою OpenTelemetry. Для довідки, повний приклад коду, який ви будете створювати, можна знайти тут: opentelemetry-erlang-contrib/examples/roll_dice_elli. Зверніть увагу, що повний приклад містить додатковий код для HTML-інтерфейсу, який ми тут не розглядатимемо.
Додаткові приклади можна знайти в документації з прикладами для Erlang.
Початкові налаштування
rebar3 new release roll_dice_elli
Залежності
Нам знадобляться ще кілька залежностей, яких немає в Elli.
opentelemetry_api: містить інтерфейси, які ви будете використовувати для інструментування свого коду. Тут визначені такі речі, якTracer.with_spanтаTracer.set_attribute.opentelemetry: містить SDK, що реалізує інтерфейси, визначені в API. Без нього всі функції в API не працюють.opentelemetry_exporter: дозволяє надсилати ваші телеметричні дані до OpenTelemetry Collector та/або до самостійно розміщених або комерційних сервісів.opentelemetry_elli: створює OpenTelemetry spans як проміжне програмне забезпечення Elli.opentelemetry_api_experimental: нестабільні частини API, що включають підтримку метрик.opentelemetry_experimental: нестабільні частини SDK, що включають підтримку метрик.
Всі вони додаються разом з elli до залежностей rebar3 та застосунків, що включаються до випуску:
{deps, [elli,
recon,
opentelemetry_api,
opentelemetry_exporter,
opentelemetry,
opentelemetry_elli,
opentelemetry_api_experimental},
opentelemetry_experimental}
]}.
{shell, [{apps, [opentelemetry_experimental, opentelemetry, roll_dice]},
{config, "config/sys.config"}]}.
{relx, [{release, {roll_dice, "0.1.0"},
[opentelemetry_exporter,
opentelemetry_experimental,
opentelemetry,
recon,
roll_dice,
sasl]}
]}.
А залежності повинні бути включені в .app.src застосунку, src/roll_dice.app.src:
{application, roll_dice,
[{description, "OpenTelemetry example application"},
{vsn, "0.1.0"},
{registered, []},
{mod, {roll_dice_app, []}},
{applications,
[kernel,
stdlib,
elli,
opentelemetry_api,
opentelemetry_api_experimental,
opentelemetry_elli
]},
{env,[]},
{modules, []},
{licenses, ["Apache-2.0"]},
{links, []}
]}.
Конфігурація
SDK та експериментальний SDK налаштовуються у файлі config/sys.config:
{opentelemetry,
[{span_processor, batch},
{traces_exporter, {otel_exporter_stdout, []}}]},
{opentelemetry_experimental,
[{readers, [#{module => otel_metric_reader,
config => #{export_interval_ms => 1000,
exporter => {otel_metric_exporter_console, #{}}}}]}]},
З цією конфігурацією завершені відрізки та записані показники будуть виводитися в консоль кожну секунду.
Сервер Elli
HTTP-сервер запускається в супервізорі верхнього рівня застосунку, src/roll_dice_sup.erl:
init([]) ->
Port = list_to_integer(os:getenv("PORT", "3000")),
ElliOpts = [{callback, elli_middleware},
{callback_args, [{mods, [{roll_dice_handler, []}]}]},
{port, Port}],
ChildSpecs = [#{id => roll_dice_http,
start => {elli, start_link, [ElliOpts]},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [roll_dice_handler]}],
{ok, {SupFlags, ChildSpecs}}.
Обробник roll_dice_handler потребує функції handle, яка приймає запит GET і повертає випадковий результат кидка кубика:
handle(Req, _Args) ->
handle(Req#req.method, elli_request:path(Req), Req).
handle('GET', [~"rolldice"], _Req) ->
Roll = do_roll(),
{ok, [], erlang:integer_to_binary(Roll)}.
do_roll/0 повертає випадкове число від 1 до 6:
-spec do_roll() -> integer().
do_roll() ->
rand:uniform(6).
Інструментування
Першим кроком в інструментуванні є додавання бібліотеки інструментації Elli, otel_elli_middleware:
{callback_args, [{mods, [{otel_elli_middleware, []},
{roll_dice_handler, []}]}]},
Потім в обробнику імʼя діапазону, створеного обробником, слід оновити відповідно до семантичних домовленостей для HTTP:
handle('GET', [~"rolldice"], _Req) ->
?update_name(~"GET /rolldice"),
Roll = do_roll(),
{ok, [], erlang:integer_to_binary(Roll)}.
handle_event(_Event, _Data, _Args) ->
ok.
%%
-spec do_roll() -> integer().
do_roll() ->
?with_span(dice_roll, #{},
fun(_) ->
Roll = rand:uniform(6),
?set_attribute('roll.value', Roll),
?counter_add(?ROLL_COUNTER, 1, #{'roll.value' => Roll}),
Roll
end).
Останній код, який нам потрібен, — це створення інструментів, ROLL_COUNTER у roll_dice_app.erl:
-include_lib("opentelemetry_api_experimental/include/otel_meter.hrl").
start(_StartType, _StartArgs) ->
create_instruments(),
roll_dice_sup:start_link().
create_instruments() ->
?create_counter(?ROLL_COUNTER, #{description => ~"The number of rolls by roll value.",
unit => '1'}).
Спробуйте
rebar3 shell
Тепер, якщо ви вкажете у своєму оглядачі/curl/тощо адресу localhost:3000/rolldice, ви повинні отримати у відповідь випадкове число, а також 3 відрізки та 1 метрику у своїй консолі.
Переглянути повні відрізки
roll_counter{roll.value=1} 1
{span,319413853664572622578356032097465423781,9329051549219651155,
{tracestate,[]},
4483837830122616505,true,dice_roll,internal,-576460743866039000,
-576460743861510287, {attributes,128,infinity,0,#{'roll.value' => 1}},
{events,128,128,infinity,0,[]}, {links,128,128,infinity,0,[]},
undefined,1,false,
{instrumentation_scope,<<"roll_dice">>,<<"0.1.0">>,undefined}}
{span,120980994633230227841304483210494731701,17581728945491241369,
{tracestate,[]}, undefined,undefined,<<"GET /">>,server,-576460745567307647,
-576460745552778124, {attributes,128,infinity,0, #{<<"http.flavor">> =>
<<"1.1">>, <<"http.host">> => <<"localhost:3000">>, <<"http.method">> =>
<<"GET">>, <<"http.response_content_length">> => 428, <<"http.status">> =>
200,<<"http.target">> => <<"/">>, <<"http.user_agent">> => <<"Mozilla/5.0 (X11;
Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0">>, <<"net.host.ip">> =>
<<"127.0.0.1">>, <<"net.host.name">> => "rosa",<<"net.host.port">> => 3000,
<<"net.peer.ip">> => <<"127.0.0.1">>, <<"net.peer.name">> =>
<<"localhost:3000">>, <<"net.peer.port">> => 34112, <<"net.transport">> =>
<<"IP.TCP">>}}, {events,128,128,infinity,0,[]}, {links,128,128,infinity,0,[]},
{status,unset,<<>>}, 1,false,
{instrumentation_scope,<<"opentelemetry_elli">>,<<"0.2.0">>,undefined}}
{span,99954316162469909244758406078309269908,7583363800346194390,
{tracestate,[]}, undefined,undefined,<<"HTTP GET">>,server,-576460745388883955,
-576460745387339610, {attributes,128,infinity,0, #{<<"http.flavor">> =>
<<"1.1">>, <<"http.host">> => <<"localhost:3000">>, <<"http.method">> =>
<<"GET">>, <<"http.response_content_length">> => 457642, <<"http.status">> =>
200, <<"http.target">> => <<"/static/index.js">>, <<"http.user_agent">> =>
<<"Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0">>,
<<"net.host.ip">> => <<"127.0.0.1">>, <<"net.host.name">> =>
"rosa",<<"net.host.port">> => 3000, <<"net.peer.ip">> => <<"127.0.0.1">>,
<<"net.peer.name">> => <<"localhost:3000">>, <<"net.peer.port">> => 34112,
<<"net.transport">> => <<"IP.TCP">>}}, {events,128,128,infinity,0,[]},
{links,128,128,infinity,0,[]}, {status,unset,<<>>}, 1,false,
{instrumentation_scope,<<"opentelemetry_elli">>,<<"0.2.0">>,undefined}}
{span,319413853664572622578356032097465423781,4483837830122616505,
{tracestate,[]}, 4897145615278856533,true,<<"GET
/rolldice">>,server,-576460743866475748, -576460743861225124,
{attributes,128,infinity,0, #{<<"http.flavor">> => <<"1.1">>, <<"http.host">> =>
<<"localhost:3000">>, <<"http.method">> => <<"GET">>,
<<"http.response_content_length">> => 1, <<"http.status">> => 200,
<<"http.target">> => <<"/rolldice">>, <<"http.user_agent">> => <<"Mozilla/5.0
(X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0">>, <<"net.host.ip">>
=> <<"127.0.0.1">>, <<"net.host.name">> => "rosa",<<"net.host.port">> => 3000,
<<"net.peer.ip">> => <<"127.0.0.1">>, <<"net.peer.name">> =>
<<"localhost:3000">>, <<"net.peer.port">> => 34112, <<"net.transport">> =>
<<"IP.TCP">>}}, {events,128,128,infinity,0,[]}, {links,128,128,infinity,0,[]},
{status,unset,<<>>}, 1,false,
{instrumentation_scope,<<"opentelemetry_elli">>,<<"0.2.0">>,undefined}}
Наступні кроки
Збагачуйте ваше інструментування більшою кількістю ручного інструментування.
Вам також потрібно буде налаштувати відповідний експортер для експорту ваших даних телеметрії до одного або більше бекендів телеметрії.
Відгук
Чи це було корисним?
Дякуємо. Ми цінуємо ваші відгуки!
Будь ласка, дайте нам знати як ми можемо покращити цю сторінку. Ми цінуємо ваші відгуки!