towerops/lib/towerops_web/telemetry.ex
Graham McIntire b53a53b199
Add comprehensive Dialyzer type specifications
- Add @type definitions to all schema modules:
  - User, UserCredential, Membership, Invitation
  - Organization, Site, AgentAssignment
  - InterfaceStat, SensorReading, Alert
- Fix all compilation warnings with stronger Elixir types:
  - Remove unused require Logger in log_filter.ex
  - Remove unused parse_float(nil) clause
  - Add pin operators (^) for variables in binary pattern matches
- Fix Dialyzer errors (25 → 0):
  - Remove unreachable pattern match clauses
  - Fix unmatched return values with _ = prefix
  - Update @spec for deliver_alert_notification/1
- Properly handle all Phoenix.PubSub.subscribe and Task.start return values
- Explicitly ignore if statement return values where needed

All files now pass mix compile --warnings-as-errors and mix dialyzer.
2026-01-17 10:52:02 -06:00

152 lines
4.7 KiB
Elixir

defmodule ToweropsWeb.Telemetry do
@moduledoc false
use Supervisor
import Telemetry.Metrics
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
end
@impl true
def init(_arg) do
# Attach telemetry handlers for logging request failures
_ =
:telemetry.attach(
"towerops-router-exception",
[:phoenix, :router_dispatch, :exception],
&__MODULE__.handle_router_exception/4,
nil
)
_ =
:telemetry.attach(
"towerops-endpoint-stop",
[:phoenix, :endpoint, :stop],
&__MODULE__.handle_endpoint_stop/4,
nil
)
children = [
# Telemetry poller will execute the given period measurements
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
# Add reporters as children of your supervision tree.
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
]
Supervisor.init(children, strategy: :one_for_one)
end
def metrics do
[
# Phoenix Metrics
summary("phoenix.endpoint.start.system_time",
unit: {:native, :millisecond}
),
summary("phoenix.endpoint.stop.duration",
unit: {:native, :millisecond}
),
summary("phoenix.router_dispatch.start.system_time",
tags: [:route],
unit: {:native, :millisecond}
),
summary("phoenix.router_dispatch.exception.duration",
tags: [:route],
unit: {:native, :millisecond}
),
summary("phoenix.router_dispatch.stop.duration",
tags: [:route],
unit: {:native, :millisecond}
),
summary("phoenix.socket_connected.duration",
unit: {:native, :millisecond}
),
sum("phoenix.socket_drain.count"),
summary("phoenix.channel_joined.duration",
unit: {:native, :millisecond}
),
summary("phoenix.channel_handled_in.duration",
tags: [:event],
unit: {:native, :millisecond}
),
# Database Metrics
summary("towerops.repo.query.total_time",
unit: {:native, :millisecond},
description: "The sum of the other measurements"
),
summary("towerops.repo.query.decode_time",
unit: {:native, :millisecond},
description: "The time spent decoding the data received from the database"
),
summary("towerops.repo.query.query_time",
unit: {:native, :millisecond},
description: "The time spent executing the query"
),
summary("towerops.repo.query.queue_time",
unit: {:native, :millisecond},
description: "The time spent waiting for a database connection"
),
summary("towerops.repo.query.idle_time",
unit: {:native, :millisecond},
description: "The time the connection spent waiting before being checked out for the query"
),
# VM Metrics
summary("vm.memory.total", unit: {:byte, :kilobyte}),
summary("vm.total_run_queue_lengths.total"),
summary("vm.total_run_queue_lengths.cpu"),
summary("vm.total_run_queue_lengths.io")
]
end
defp periodic_measurements do
[
# A module, function and arguments to be invoked periodically.
# This function must call :telemetry.execute/3 and a metric must be added above.
# {ToweropsWeb, :count_users, []}
]
end
# Telemetry handler for router exceptions
def handle_router_exception(_event, _measurements, metadata, _config) do
require Logger
Logger.error(
"Router exception on #{metadata.plug} #{metadata.conn.method} #{metadata.conn.request_path}",
kind: metadata.kind,
reason: metadata.reason,
stacktrace: metadata.stacktrace,
request_id: metadata.conn.assigns[:request_id]
)
end
# Telemetry handler for endpoint stop events (log slow requests and errors)
def handle_endpoint_stop(_event, measurements, metadata, _config) do
require Logger
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
# Log slow requests (over 5 seconds)
if duration_ms > 5_000 do
Logger.warning(
"Slow request: #{metadata.conn.method} #{metadata.conn.request_path} took #{duration_ms}ms",
request_id: metadata.conn.assigns[:request_id],
duration_ms: duration_ms
)
end
# Log requests with non-2xx status codes
status = metadata.conn.status
if status >= 500 do
Logger.error(
"Server error: #{metadata.conn.method} #{metadata.conn.request_path} returned #{status}",
request_id: metadata.conn.assigns[:request_id],
status: status,
duration_ms: duration_ms
)
end
end
end