-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
replace geolix with locus #2362
Changes from all commits
a6267c0
7a1c53c
2a0ac99
f386e38
07d55dd
898978c
1cf5f54
4ef0a61
0a4d8a8
18a904c
c775070
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,9 +30,14 @@ defmodule Plausible.Application do | |
] | ||
|
||
opts = [strategy: :one_for_one, name: Plausible.Supervisor] | ||
|
||
setup_sentry() | ||
setup_opentelemetry() | ||
|
||
setup_geolocation() | ||
Location.load_all() | ||
Plausible.Geo.await_loader() | ||
|
||
Supervisor.start_link(children, opts) | ||
end | ||
|
||
|
@@ -119,4 +124,9 @@ defmodule Plausible.Application do | |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this seems to be async by default, I think it would also be a good idea to add this loading step to the Side question: how long does it take to download and be ready to run geolocation lookups with the Maxmind license key? Side note: not a concern for this PR but I think we're getting to a point where we should really separate liveness vs readiness probes as suggested by @cnkk. Loading the geolocation DB is required for the app to be 'ready' but it's independent from it being 'live'. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's sync by default in this implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On my laptop it takes six seconds. I don't know how much bigger the paid version is though. I think most of the time is spent on IO, decoding (or rather, verification) is fast. Mix.install [:locus]
:timer.tc fn ->
Application.put_env(:locus, :license_key, "XXdDMc5OhchOTazu") # I'll delete it in a few days
:ok = :locus.start_loader(:city, {:maxmind, "GeoLite2-City"}, [:no_cache])
{:ok, _} = :locus.await_loader(:city)
end
#=> {6204296, {:ok, {{2023, 1, 10}, {15, 33, 48}}}} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm. Seeing as this is sync and called before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, maybe we can fire it off before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's a good idea There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What was the problem? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem was that I was using it wrong 😅. Fixed in c775070. Let me know what you think :) |
||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
defmodule Plausible.Geo do | ||
@moduledoc """ | ||
This module provides an API for fetching IP geolocation. | ||
""" | ||
|
||
require Logger | ||
|
||
@db :geolocation | ||
|
||
@doc """ | ||
Starts the geodatabase loading process. Two modes are supported: local file | ||
and MaxMind license key. | ||
|
||
## Options | ||
|
||
* `:path` - the path to the .mmdb database local file. When present, | ||
`:license_key` and `:edition` are not required. | ||
|
||
* `:license_key` - the [license key](https://support.maxmind.com/hc/en-us/articles/4407111582235-Generate-a-License-Key) | ||
from MaxMind to authenticate requests to MaxMind. | ||
|
||
* `:edition` - the name of the MaxMind database to be downloaded from MaxMind | ||
servers. Defaults to `GeoLite2-City`. | ||
|
||
* `:async` - when used, configures the database loading to run | ||
asynchronously. | ||
|
||
## Examples | ||
|
||
Loading from a local file: | ||
|
||
iex> load_db(path: "/etc/plausible/dbip-city.mmdb") | ||
:ok | ||
|
||
Downloading a MaxMind DB (this license key is no longer active): | ||
|
||
iex> load_db(license_key: "LNpsJCCKPis6XvBP", edition: "GeoLite2-City", async: true) | ||
:ok | ||
|
||
""" | ||
def load_db(opts) do | ||
cond do | ||
license_key = opts[:license_key] -> | ||
edition = opts[:edition] || "GeoLite2-City" | ||
:ok = :locus.start_loader(@db, {:maxmind, edition}, license_key: license_key) | ||
|
||
path = opts[:path] -> | ||
:ok = :locus.start_loader(@db, path) | ||
|
||
true -> | ||
raise "failed to load geolocation db: need :path or :license_key to be provided" | ||
end | ||
|
||
unless opts[:async] do | ||
{:ok, _version} = :locus.await_loader(@db) | ||
end | ||
ukutaht marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
:ok | ||
end | ||
|
||
@doc """ | ||
Waits for the database to start after calling `load_db/1` with the async option. | ||
""" | ||
def await_loader, do: :locus.await_loader(@db) | ||
|
||
@doc """ | ||
Returns geodatabase type. | ||
|
||
Used for deciding whether to show the DB-IP disclaimer or not. | ||
|
||
## Examples | ||
|
||
In the case of a DB-IP database: | ||
|
||
iex> database_type() | ||
"DBIP-City-Lite" | ||
|
||
In the case of a MaxMind database: | ||
|
||
iex> database_type() | ||
"GeoLite2-City" | ||
|
||
""" | ||
def database_type do | ||
case :locus.get_info(@db, :metadata) do | ||
{:ok, %{database_type: type}} -> type | ||
_other -> nil | ||
end | ||
end | ||
|
||
@doc """ | ||
Looks up geo info about an IP address. | ||
|
||
## Examples | ||
|
||
iex> lookup("8.7.6.5") | ||
%{ | ||
"city" => %{ | ||
"geoname_id" => 5349755, | ||
"names" => %{ | ||
"de" => "Fontana", | ||
"en" => "Fontana", | ||
"ja" => "フォンタナ", | ||
"ru" => "Фонтана" | ||
} | ||
}, | ||
"continent" => %{ | ||
"code" => "NA", | ||
"geoname_id" => 6255149, | ||
"names" => %{ | ||
"de" => "Nordamerika", | ||
"en" => "North America", | ||
"es" => "Norteamérica", | ||
"fr" => "Amérique du Nord", | ||
"ja" => "北アメリカ", | ||
"pt-BR" => "América do Norte", | ||
"ru" => "Северная Америка", | ||
"zh-CN" => "北美洲" | ||
} | ||
}, | ||
"country" => %{ | ||
"geoname_id" => 6252001, | ||
"iso_code" => "US", | ||
"names" => %{ | ||
"de" => "Vereinigte Staaten", | ||
"en" => "United States", | ||
"es" => "Estados Unidos", | ||
"fr" => "États Unis", | ||
"ja" => "アメリカ", | ||
"pt-BR" => "EUA", | ||
"ru" => "США", | ||
"zh-CN" => "美国" | ||
} | ||
}, | ||
"location" => %{ | ||
"accuracy_radius" => 50, | ||
"latitude" => 34.1211, | ||
"longitude" => -117.4362, | ||
"metro_code" => 803, | ||
"time_zone" => "America/Los_Angeles" | ||
}, | ||
"postal" => %{"code" => "92336"}, | ||
"registered_country" => %{ | ||
"geoname_id" => 6252001, | ||
"iso_code" => "US", | ||
"names" => %{ | ||
"de" => "Vereinigte Staaten", | ||
"en" => "United States", | ||
"es" => "Estados Unidos", | ||
"fr" => "États Unis", | ||
"ja" => "アメリカ", | ||
"pt-BR" => "EUA", | ||
"ru" => "США", | ||
"zh-CN" => "美国" | ||
} | ||
}, | ||
"subdivisions" => [ | ||
%{ | ||
"geoname_id" => 5332921, | ||
"iso_code" => "CA", | ||
"names" => %{ | ||
"de" => "Kalifornien", | ||
"en" => "California", | ||
"es" => "California", | ||
"fr" => "Californie", | ||
"ja" => "カリフォルニア州", | ||
"pt-BR" => "Califórnia", | ||
"ru" => "Калифорния", | ||
"zh-CN" => "加州" | ||
} | ||
} | ||
] | ||
} | ||
|
||
""" | ||
def lookup(ip_address) do | ||
case :locus.lookup(@db, ip_address) do | ||
{:ok, entry} -> | ||
entry | ||
|
||
:not_found -> | ||
nil | ||
|
||
{:error, reason} -> | ||
Logger.error("failed to lookup ip address: " <> inspect(reason)) | ||
nil | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
locus
trusts the file extension.locus
didn't try to gunzipdbip-country.mmdb
because it didn't have a.gz
suffix.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, the error that
locus
logged was