Dialup は :telemetry ライブラリを使って計測ポイントを提供する。WebSocket 接続数、イベント処理時間、エラー率などを監視できる。
イベント一覧
WebSocket
| イベント | 計測値 | メタデータ |
|---|---|---|
[:dialup, :websocket, :connect] | %{count: 1} | %{session_id: "..."} |
[:dialup, :websocket, :disconnect] | %{count: 1} | %{session_id: "..."} |
イベント処理
| イベント | 計測値 | メタデータ |
|---|---|---|
[:dialup, :event, :start] | %{system_time: integer} | %{event: "increment", path: "/"} |
[:dialup, :event, :stop] | %{duration: integer} | %{event: "increment", path: "/"} |
[:dialup, :event, :exception] | %{duration: integer} | %{event: "increment", path: "/", kind: :error, reason: %RuntimeError{}, stacktrace: [...]} |
duration は System.monotonic_time() のネイティブ単位。ミリ秒に変換するには System.convert_time_unit(duration, :native, :millisecond) を使う。
すべての start に対して stop か exception のどちらかが必ず発火する。
ナビゲーション
| イベント | 計測値 | メタデータ |
|---|---|---|
[:dialup, :navigate, :start] | %{system_time: integer} | %{path: "/users/123"} |
[:dialup, :navigate, :stop] | %{duration: integer} | %{path: "/users/123"} |
[:dialup, :navigate, :exception] | %{duration: integer} | %{path: "/users/123", kind: :error, reason: %RuntimeError{}, stacktrace: [...]} |
ハンドラの登録
アプリケーション起動時に :telemetry.attach_many でハンドラを登録する。
重要: use Dialup を含むモジュールはホットリロード時に再コンパイルされるため、テレメトリハンドラは別モジュールに切り出す必要がある。同じモジュール内に defp で定義すると、関数参照が無効化されて :badfun エラーになる。
# lib/my_app.ex
defmodule MyApp do
use Application
use Dialup,
app_dir: __DIR__ <> "/app",
title: "My App"
@impl Application
def start(_type, _args) do
MyApp.Telemetry.attach()
children = [{Dialup, app: __MODULE__, port: 4000}]
Supervisor.start_link(children, strategy: :one_for_one)
end
end# lib/telemetry.ex
defmodule MyApp.Telemetry do
require Logger
def attach do
:telemetry.attach_many(
"my-app-telemetry",
[
[:dialup, :websocket, :connect],
[:dialup, :websocket, :disconnect],
[:dialup, :event, :stop],
[:dialup, :event, :exception],
[:dialup, :navigate, :stop],
[:dialup, :navigate, :exception]
],
&__MODULE__.handle/4,
nil
)
end
def handle([:dialup, :websocket, :connect], _measurements, meta, _config) do
Logger.info("[ws] connected: #{meta.session_id}")
end
def handle([:dialup, :websocket, :disconnect], _measurements, meta, _config) do
Logger.info("[ws] disconnected: #{meta.session_id}")
end
def handle([:dialup, :event, :stop], %{duration: d}, meta, _config) do
ms = System.convert_time_unit(d, :native, :millisecond)
Logger.info("[event] #{meta.event} on #{meta.path} (#{ms}ms)")
end
def handle([:dialup, :event, :exception], %{duration: d}, meta, _config) do
ms = System.convert_time_unit(d, :native, :millisecond)
Logger.error("[event] #{meta.event} on #{meta.path} failed (#{ms}ms): #{Exception.message(meta.reason)}")
end
def handle([:dialup, :navigate, :stop], %{duration: d}, meta, _config) do
ms = System.convert_time_unit(d, :native, :millisecond)
Logger.info("[navigate] #{meta.path} (#{ms}ms)")
end
def handle([:dialup, :navigate, :exception], %{duration: d}, meta, _config) do
ms = System.convert_time_unit(d, :native, :millisecond)
Logger.error("[navigate] #{meta.path} failed (#{ms}ms): #{Exception.message(meta.reason)}")
end
def handle(_event, _measurements, _meta, _config), do: :ok
endtelemetry_metrics との連携
Telemetry.Metrics を使ったダッシュボード向けのメトリクス定義例:
defp metrics do
[
Telemetry.Metrics.counter("dialup.websocket.connect.count"),
Telemetry.Metrics.counter("dialup.websocket.disconnect.count"),
Telemetry.Metrics.distribution("dialup.event.stop.duration",
unit: {:native, :millisecond},
tags: [:event, :path]
),
Telemetry.Metrics.counter("dialup.event.exception.duration",
tags: [:event, :path]
),
Telemetry.Metrics.distribution("dialup.navigate.stop.duration",
unit: {:native, :millisecond},
tags: [:path]
)
]
end