Module: Sentiero::Geo
- Defined in:
- lib/sentiero/geo.rb
Overview
Deliberately coarse — country/city/region/timezone, no coordinates, no IP: resolution happens in-request and only the result is stored, so config.anonymize_ip is unaffected.
Constant Summary collapse
- MAX_VALUE_LENGTH =
256- PROC_WARNING_LOCK =
Mutex.new
- CLOUDFLARE_HEADERS =
CF-IPCountry ships with Cloudflare IP geolocation; the other headers need the "Add visitor location headers" managed transform, so country-only is the common case.
{ "geo_country" => "HTTP_CF_IPCOUNTRY", "geo_city" => "HTTP_CF_IPCITY", "geo_region" => "HTTP_CF_REGION", "geo_timezone" => "HTTP_CF_TIMEZONE" }.freeze
- CLOUDFLARE_UNKNOWN =
Cloudflare placeholder codes: XX = unknown, T1 = Tor exit.
%w[XX T1].freeze
- PROC_KEY_MAP =
{ "country" => "geo_country", "city" => "geo_city", "region" => "geo_region", "timezone" => "geo_timezone" }.freeze
- TARGET_KEYS =
PROC_KEY_MAP.values.freeze
Class Method Summary collapse
- .clean(value) ⇒ Object
- .from_cloudflare(env) ⇒ Object
-
.from_proc(env, source, client_metadata = {}) ⇒ Object
A broken geo hook must never break ingest: rescue everything, warn once per process, resolve empty.
- .reset_proc_warning! ⇒ Object
- .resolve(env, source, client_metadata = {}) ⇒ Object
Class Method Details
.clean(value) ⇒ Object
77 78 79 80 81 82 83 84 |
# File 'lib/sentiero/geo.rb', line 77 def clean(value) return nil unless value.is_a?(String) stripped = value.strip return nil if stripped.empty? stripped[0, MAX_VALUE_LENGTH] end |
.from_cloudflare(env) ⇒ Object
43 44 45 46 47 48 49 50 |
# File 'lib/sentiero/geo.rb', line 43 def from_cloudflare(env) geo = CLOUDFLARE_HEADERS.each_with_object({}) do |(key, header), acc| value = clean(env[header]) acc[key] = value if value end geo.delete("geo_country") if CLOUDFLARE_UNKNOWN.include?(geo["geo_country"]) geo end |
.from_proc(env, source, client_metadata = {}) ⇒ Object
A broken geo hook must never break ingest: rescue everything, warn once per process, resolve empty. A two-arg resolver opts into the client metadata.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/sentiero/geo.rb', line 54 def from_proc(env, source, = {}) raw = (source.arity == 1) ? source.call(env) : source.call(env, ) return {} unless raw.is_a?(Hash) raw.each_with_object({}) do |(key, value), acc| mapped = PROC_KEY_MAP[key.to_s] value = clean(value) acc[mapped] = value if mapped && value end rescue => e PROC_WARNING_LOCK.synchronize do return {} if @proc_warning_emitted @proc_warning_emitted = true end warn "[Sentiero] geo_source raised #{e.class}: #{e.}; skipping geo capture" {} end |
.reset_proc_warning! ⇒ Object
73 74 75 |
# File 'lib/sentiero/geo.rb', line 73 def reset_proc_warning! @proc_warning_emitted = false end |
.resolve(env, source, client_metadata = {}) ⇒ Object
35 36 37 38 39 40 41 |
# File 'lib/sentiero/geo.rb', line 35 def resolve(env, source, = {}) case source when nil then {} when :cloudflare then from_cloudflare(env) else from_proc(env, source, ) end end |