analytics/lib/plausible/application.ex

401 lines
12 KiB
Elixir

defmodule Plausible.Application do
@moduledoc false
use Application
use Plausible
require Logger
def start(_type, _args) do
on_ee(do: Plausible.License.ensure_valid_license())
on_ce(do: :inet_db.set_tcp_module(:happy_tcp))
# in CE we start the endpoint under site_encrypt for automatic https
endpoint = on_ee(do: PlausibleWeb.Endpoint, else: maybe_https_endpoint())
cluster =
on_ee(
do:
{Cluster.Supervisor,
[
[
default: [
strategy: Cluster.Strategy.ErlangHosts,
config: [timeout: 30_000]
]
],
[name: Plausible.ClusterSupervisor]
]}
)
children =
[
cluster,
{PartitionSupervisor,
child_spec: Task.Supervisor, name: Plausible.UserAgentParseTaskSupervisor},
Plausible.Session.BalancerSupervisor,
Plausible.PromEx,
{Plausible.Auth.TOTP.Vault, key: totp_vault_key()},
Plausible.Repo,
Plausible.ClickhouseRepo,
Plausible.IngestRepo,
Plausible.AsyncInsertRepo,
Plausible.ImportDeletionRepo,
Plausible.Cache.Adapter.child_spec(:customer_currency, :cache_customer_currency,
ttl_check_interval: :timer.minutes(5),
n_lock_partitions: 1,
global_ttl: :timer.minutes(60)
),
Plausible.Cache.Adapter.child_spec(:user_agents, :cache_user_agents,
ttl_check_interval: :timer.minutes(5),
global_ttl: :timer.minutes(60),
n_lock_partitions: 1,
ets_options: [read_concurrency: true, write_concurrency: true]
),
Plausible.Cache.Adapter.child_specs(:sessions, :cache_sessions,
ttl_check_interval: :timer.seconds(10),
global_ttl: :timer.minutes(30),
n_lock_partitions: 1,
ets_options: [read_concurrency: true, write_concurrency: true]
),
{Plausible.Session.Transfer,
base_path: Application.get_env(:plausible, :session_transfer_dir)},
warmed_cache(Plausible.Site.Cache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
ets_options: [read_concurrency: true]
],
warmers: [
refresh_all:
{Plausible.Site.Cache.All,
interval: :timer.minutes(15) + Enum.random(1..:timer.seconds(10))},
refresh_updated_recently:
{Plausible.Site.Cache.RecentlyUpdated, interval: :timer.seconds(30)}
]
),
on_ee do
warmed_cache(Plausible.ConsolidatedView.Cache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
ets_options: [read_concurrency: true]
],
warmers: [
refresh_all:
{Plausible.ConsolidatedView.Cache.All,
interval: :timer.minutes(20) + Enum.random(1..:timer.seconds(10))},
refresh_updated_recently:
{Plausible.ConsolidatedView.Cache.RecentlyUpdated, interval: :timer.minutes(1)}
]
)
end,
warmed_cache(Plausible.Shield.IPRuleCache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
ets_options: [read_concurrency: true]
],
warmers: [
refresh_all:
{Plausible.Shield.IPRuleCache.All,
interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))},
refresh_updated_recently:
{Plausible.Shield.IPRuleCache.RecentlyUpdated, interval: :timer.seconds(35)}
]
),
warmed_cache(Plausible.Shield.CountryRuleCache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
ets_options: [read_concurrency: true]
],
warmers: [
refresh_all:
{Plausible.Shield.CountryRuleCache.All,
interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))},
refresh_updated_recently:
{Plausible.Shield.CountryRuleCache.RecentlyUpdated, interval: :timer.seconds(35)}
]
),
warmed_cache(Plausible.Shield.PageRuleCache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
ets_options: [:bag, read_concurrency: true]
],
warmers: [
refresh_all:
{Plausible.Shield.PageRuleCache.All,
interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))},
refresh_updated_recently:
{Plausible.Shield.PageRuleCache.RecentlyUpdated, interval: :timer.seconds(35)}
]
),
warmed_cache(Plausible.Shield.HostnameRuleCache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
ets_options: [:bag, read_concurrency: true]
],
warmers: [
refresh_all:
{Plausible.Shield.HostnameRuleCache.All,
interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))},
refresh_updated_recently:
{Plausible.Shield.HostnameRuleCache.RecentlyUpdated, interval: :timer.seconds(25)}
]
),
on_ee do
warmed_cache(Plausible.Stats.SamplingCache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
read_concurrency: true
],
warmers: [
refresh_all:
{Plausible.Stats.SamplingCache.All,
interval: :timer.hours(24) + Enum.random(1..:timer.minutes(60))}
]
)
end,
warmed_cache(PlausibleWeb.TrackerScriptCache,
adapter_opts: [
n_lock_partitions: 1,
ttl_check_interval: false,
ets_options: [:bag, read_concurrency: true]
],
warmers: [
refresh_all:
{PlausibleWeb.TrackerScriptCache.All,
interval: :timer.minutes(180) + Enum.random(1..:timer.seconds(10))},
refresh_updated_recently:
{PlausibleWeb.TrackerScriptCache.RecentlyUpdated, interval: :timer.seconds(30)}
]
),
Plausible.Ingestion.Counters,
Plausible.Session.Salts,
Supervisor.child_spec(Plausible.Event.WriteBuffer, id: Plausible.Event.WriteBuffer),
Supervisor.child_spec(Plausible.Session.WriteBuffer, id: Plausible.Session.WriteBuffer),
ReferrerBlocklist,
{Plausible.RateLimit, clean_period: :timer.minutes(10)},
{Finch, name: Plausible.Finch, pools: finch_pool_config()},
{Phoenix.PubSub, name: Plausible.PubSub},
endpoint,
{Oban, Application.get_env(:plausible, Oban)},
on_ee do
help_scout_vault()
end
]
|> List.flatten()
|> Enum.reject(&is_nil/1)
opts = [strategy: :one_for_one, name: Plausible.Supervisor]
setup_request_logging()
setup_sentry()
setup_opentelemetry()
Plausible.Ingestion.Persistor.TelemetryHandler.install()
setup_geolocation()
Location.load_all()
Plausible.Geo.await_loader()
Supervisor.start_link(List.flatten(children), opts)
end
def config_change(changed, _new, removed) do
PlausibleWeb.Endpoint.config_change(changed, removed)
:ok
end
on_ee do
defp help_scout_vault() do
help_scout_vault_key =
:plausible
|> Application.fetch_env!(Plausible.HelpScout)
|> Keyword.fetch!(:vault_key)
|> Base.decode64!()
[{Plausible.HelpScout.Vault, key: help_scout_vault_key}]
end
end
defp totp_vault_key() do
:plausible
|> Application.fetch_env!(Plausible.Auth.TOTP)
|> Keyword.fetch!(:vault_key)
end
defp finch_pool_config() do
default = Application.get_env(:plausible, Plausible.Finch)
base_config =
if default do
%{default: default}
else
%{}
end
default_opts = default || []
base_config
|> Map.put(
"https://icons.duckduckgo.com",
Config.Reader.merge(default_opts, conn_opts: [transport_opts: [timeout: 15_000]])
)
|> maybe_add_persistor_pool(default_opts)
|> maybe_add_sentry_pool(default_opts)
|> maybe_add_paddle_pool(default_opts)
|> maybe_add_google_pools(default_opts)
end
defp maybe_add_sentry_pool(pool_config, default) do
case Sentry.Config.dsn() do
%{endpoint_uri: "http" <> _rest = url} ->
Map.put(pool_config, url, Config.Reader.merge(default, size: 50))
nil ->
pool_config
end
end
defp maybe_add_persistor_pool(pool_config, default) do
backend =
:plausible
|> Application.fetch_env!(Plausible.Ingestion.Persistor)
|> Keyword.fetch!(:backend)
persistor_conf = Application.get_env(:plausible, Plausible.Ingestion.Persistor.Remote)
if backend in [
Plausible.Ingestion.Persistor.Remote,
Plausible.Ingestion.Persistor.EmbeddedWithRelay
] do
persistor_url = Keyword.fetch!(persistor_conf, :url)
count = Keyword.fetch!(persistor_conf, :count)
timeout_ms = Keyword.fetch!(persistor_conf, :timeout_ms)
Map.put(
pool_config,
persistor_url,
Config.Reader.merge(
default,
protocols: [:http2],
count: count,
conn_opts: [transport_opts: [timeout: timeout_ms, nodelay: false]]
)
)
else
pool_config
end
end
defp maybe_add_paddle_pool(pool_config, default) do
paddle_conf = Application.get_env(:plausible, :paddle)
cond do
paddle_conf[:vendor_id] && paddle_conf[:vendor_auth_code] ->
Map.put(
pool_config,
Plausible.Billing.PaddleApi.vendors_domain(),
Config.Reader.merge(default, conn_opts: [transport_opts: [timeout: 15_000]])
)
true ->
pool_config
end
end
defp maybe_add_google_pools(pool_config, default) do
google_conf = Application.get_env(:plausible, :google)
cond do
google_conf[:client_id] && google_conf[:client_secret] ->
pool_config
|> Map.put(
google_conf[:api_url],
Config.Reader.merge(default, conn_opts: [transport_opts: [timeout: 15_000]])
)
|> Map.put(
google_conf[:reporting_api_url],
Config.Reader.merge(default, conn_opts: [transport_opts: [timeout: 15_000]])
)
true ->
pool_config
end
end
def setup_request_logging() do
:telemetry.attach(
"plausible-request-logging",
[:phoenix, :endpoint, :stop],
&Plausible.RequestLogger.log_request/4,
%{}
)
end
def setup_sentry() do
Logger.add_backend(Sentry.LoggerBackend)
:telemetry.attach_many(
"oban-errors",
[[:oban, :job, :exception], [:oban, :notifier, :exception], [:oban, :plugin, :exception]],
&ObanErrorReporter.handle_event/4,
%{}
)
end
defp setup_opentelemetry() do
OpentelemetryPhoenix.setup()
OpentelemetryEcto.setup([:plausible, :repo])
OpentelemetryEcto.setup([:plausible, :clickhouse_repo])
OpentelemetryOban.setup()
end
defp setup_geolocation do
opts = Application.fetch_env!(:plausible, Plausible.Geo)
:ok = Plausible.Geo.load_db(opts)
end
defp warmed_cache(impl_mod, opts) when is_atom(impl_mod) and is_list(opts) do
warmers = Keyword.fetch!(opts, :warmers)
warmer_specs =
Enum.map(warmers, fn {warmer_fn, {warmer_id, warmer_opts}} ->
{Plausible.Cache.Warmer,
Keyword.merge(
[
child_name: warmer_id,
cache_impl: impl_mod,
warmer_fn: warmer_fn
],
warmer_opts
)}
end)
[{impl_mod, Keyword.fetch!(opts, :adapter_opts)} | warmer_specs]
end
on_ce do
defp maybe_https_endpoint do
endpoint_config = Application.fetch_env!(:plausible, PlausibleWeb.Endpoint)
selfhost_config = Application.fetch_env!(:plausible, :selfhost)
site_encrypt_config = Keyword.get(selfhost_config, :site_encrypt)
if get_in(endpoint_config, [:https, :port]) do
PlausibleWeb.Endpoint.force_https()
end
if site_encrypt_config do
PlausibleWeb.Endpoint.allow_acme_challenges()
{SiteEncrypt.Phoenix.Endpoint, endpoint: PlausibleWeb.Endpoint}
else
PlausibleWeb.Endpoint
end
end
end
end