Class: TrackRelay::Subscribers::Ga4MeasurementProtocol
- Defined in:
- lib/track_relay/subscribers/ga4_measurement_protocol.rb
Overview
GA4 Measurement Protocol server-side subscriber (REQ-08, REQ-11).
POSTs each event to Google Analytics 4 via the Measurement Protocol endpoint:
POST https://www.google-analytics.com/mp/collect
?measurement_id=G-XXXXXXXXXX
&api_secret=<secret>
Content-Type: application/json
Body: { client_id:, user_id:, timestamp_micros:, events: [{name:, params:}] }
Async by default — ‘#deliver` runs inside a DeliveryJob (loadbalanced via ActiveJob). Hosts that need inline delivery (e.g. unit-test determinism, low-traffic ingestion) can call Base.synchronous! per REQ-11.
## Configuration
Read from Configuration at *delivery time* (NOT at class-body load time) so credentials lambdas / late-bound configs work:
- `config.ga4_measurement_id` — `G-XXXXXXXXXX`
- `config.ga4_api_secret` — per-stream MP secret
- `config.ga4_use_eu_endpoint` — when `true`, post to
`https://region1.google-analytics.com/mp/collect`
When ‘ga4_measurement_id` or `ga4_api_secret` is `nil`, `#deliver` emits a single `Rails.logger.warn` and returns without raising —gem-loaded-but-not-configured apps must not crash.
## Error contract
‘#deliver` raises:
- {TrackRelay::DeliveryRetriableError} on transient failures
(HTTP 5xx, `Net::OpenTimeout`, `Net::ReadTimeout`,
`Errno::ECONNREFUSED`, `SocketError`). Mapped to
`retry_on` in {DeliveryJob}.
- {TrackRelay::DeliveryDiscardableError} on permanent failures
(HTTP 4xx — defensive: GA4 returns 2xx in practice even on
malformed payloads). Mapped to `discard_on` in {DeliveryJob}.
- {TrackRelay::Ga4ConstraintError} when call-time payload
validation fails AND `config.raise_on_validation_error` is
`true` (dev/test). In production (`raise_on_validation_error
= false`) the violation is logged via `Rails.logger.warn` and
the POST is skipped.
## Why typed retriable/discardable exceptions?
ActiveJob’s ‘retry_on`/`discard_on` macros only fire on raised exceptions, not returned values. Base#safe_deliver normally rescues any `StandardError` and returns it (the REQ-23 blanket-rescue contract), so a 5xx retry would never reach the job’s retry policy. Base therefore carves these two exception classes out of the rescue: it re-raises them so DeliveryJob can map them to ‘retry_on`/`discard_on`. See `test/unit/subscribers/base_retry_passthrough_test.rb`.
Constant Summary collapse
- ENDPOINT_URL =
GA4 production endpoint (US/global region).
"https://www.google-analytics.com/mp/collect"- ENDPOINT_URL_EU =
GA4 EU-region endpoint, selected via ‘config.ga4_use_eu_endpoint = true`.
"https://region1.google-analytics.com/mp/collect"- OPEN_TIMEOUT_SECONDS =
Net::HTTP open timeout (TCP connect).
5- READ_TIMEOUT_SECONDS =
Net::HTTP read timeout (response wait).
10- RESERVED_PARAM_PREFIXES =
GA4-reserved param-name prefixes. Per Scout §2 / REQ-27, params starting with these prefixes must not be sent — GA4 silently drops them.
%w[firebase_ ga_ google_].freeze
Instance Method Summary collapse
-
#deliver(payload) ⇒ void
POST ‘payload` to the GA4 Measurement Protocol endpoint.
Methods inherited from Base
coerce_event_set, #except_events, filter, #handle, #only_events, #safe_deliver, #set_filter_overrides!, synchronous!
Instance Method Details
#deliver(payload) ⇒ void
This method returns an undefined value.
POST ‘payload` to the GA4 Measurement Protocol endpoint.
See class docs for the full configuration / error contract.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/track_relay/subscribers/ga4_measurement_protocol.rb', line 98 def deliver(payload) config = TrackRelay.config measurement_id = config.ga4_measurement_id api_secret = config.ga4_api_secret if measurement_id.nil? || api_secret.nil? warn_missing_credentials(measurement_id, api_secret) return end return unless validate_ga4_payload!(payload) post_to_ga4(payload, measurement_id, api_secret, config.ga4_use_eu_endpoint) end |