PhoenixKitWebAnalytics.Collector (PhoenixKitWebAnalytics v0.2.0)

Copy Markdown View Source

The write path: takes a raw hit, enriches it, and stores one PhoenixKitWebAnalytics.Schemas.Event.

A hit never costs the request anything

track_async/1 hands the work to a Task.Supervisor and returns immediately, so the enrichment (a settings read, one session-stitching query, the insert) happens after the response is on its way out. The request process spends microseconds building a map.

The task supervisor is started with a max_children cap. Under a flood, once the cap is reached, further hits are dropped rather than queued — an analytics backlog must not become the reason a host runs out of database connections. Drops are logged at debug level.

Raw hit shape

Every key is optional except :path:

%{
  event_type: "pageview" | "event",   # default "pageview"
  event_name: "signup",               # required for "event"
  path: "/pricing",
  page_title: "Pricing",
  site: "myapp.com",
  referrer: "https://news.ycombinator.com/",
  query_params: %{"utm_source" => "hn"},
  ip: {127, 0, 0, 1},
  user_agent: "Mozilla/5.0 …",
  language: "en-US",
  user_uuid: "018e…",
  status: 200,
  duration_ms: 12,
  location: %{country_code: "EE"},     # pre-resolved (edge headers)
  metadata: %{"plan" => "pro"}
}

:ip and :user_agent are used for the daily visitor hash and the client classification, then discarded — see PhoenixKitWebAnalytics.Visitor.

Summary

Functions

Resolves which session a visitor's hit belongs to.

Child spec for the task supervisor that runs the async writes.

Stores a hit synchronously.

Stores a hit off the request path. Always returns :ok.

Functions

resolve_session(visitor_id, timeout_minutes, now)

@spec resolve_session(String.t(), pos_integer(), DateTime.t()) :: Ecto.UUID.t()

Resolves which session a visitor's hit belongs to.

Reuses the visitor's previous session when their last hit is within timeout_minutes, otherwise mints a new one. This is the whole reason no session cookie is needed: the stitch is an indexed lookup on (visitor_id, inserted_at), server-side.

task_supervisor_spec()

@spec task_supervisor_spec() :: Supervisor.child_spec()

Child spec for the task supervisor that runs the async writes.

Returned from PhoenixKitWebAnalytics.children/0, so a host running PhoenixKit's module supervision gets it with no configuration.

track(hit)

@spec track(map()) ::
  {:ok, PhoenixKitWebAnalytics.Schemas.Event.t()}
  | {:error, :disabled | :bot | :invalid | Ecto.Changeset.t() | term()}

Stores a hit synchronously.

Used by tests (an async task can't see the Ecto sandbox connection) and by callers that want the result. Returns {:error, :disabled} when tracking is off and {:error, :bot} when the hit was filtered as automated traffic — both are ordinary outcomes, not failures.

track_async(hit)

@spec track_async(map()) :: :ok

Stores a hit off the request path. Always returns :ok.

Falls back to an unsupervised process when the task supervisor isn't running (a host that hasn't wired PhoenixKit's module children), so tracking still works — just without the backpressure cap.