Send the request's User-Agent on backend Snowplow events

What does this MR do and why?

Server-side Snowplow events are tracked with a bare tracker Subject, so every event fired inside a web or API request reaches the collector with the Ruby tracker's own user agent. Frontend events carry the browser's User-Agent in the same field (useragent), so only requests made through the API by other clients (the GitLab mobile app, glab, editor extensions, scripts) are indistinguishable from each other and from the Web UI in the warehouse.

This MR:

  • captures the request's User-Agent in Gitlab::RequestContext, next to the client IP the Rack middleware already records;
  • passes a per-event SnowplowTracker::Subject carrying it from Gitlab::Tracking::Destinations::Snowplow#event, the single path both Gitlab::Tracking.event and Internal Events take. The tracker (snowplow-tracker 0.8.0) merges an event-level subject over its default one, so the request's User-Agent becomes the event's ua (raw) / useragent (processed) field, exactly as browser-emitted events populate it.

Outside a request (Sidekiq) there is no user agent, so the tracker's default subject stands and worker-tracked events are unchanged. No schema or context changes: useragent is a standard Snowplow atomic field that frontend events already fill, and the UA-parser enrichment applies to it as it does for browsers.

Motivation: the GitLab mobile app now sends User-Agent: GitLabMobile/<version> (<OS> <OS version>; build <n>) on every request. api_json.log records it (ua), but the Snowplow events those requests trigger did not, so the app's usage cannot be split out per version and OS in Snowflake. With this change, any API client that identifies itself gets the same attribution the browser gets.

Self-managed and Dedicated instances on 18.0+ forward structured events; after this change those include the API client's User-Agent in the same field they already forward for browser events.

References

How to set up and validate locally

  1. Run GDK with Snowplow Micro (gdk start snowplow-micro) or the Internal Events monitor:
    bundle exec rails runner scripts/internal_events/monitor.rb g_project_management_users_creating_issues
  2. Trigger a server-side event from a request with a distinctive User-Agent, for example:
    curl -H "PRIVATE-TOKEN: $TOKEN" -H "User-Agent: GitLabMobile/1.2.0 (iOS 26.0.1; build 45)" \
      -X POST "http://gdk.test:3000/api/v4/projects/<id>/issues?title=UA+test"
  3. In Snowplow Micro's UI the event's useragent is GitLabMobile/1.2.0 (iOS 26.0.1; build 45); before this change it is the Ruby tracker's ruby-tracker UA.
  4. Trigger an event from a Sidekiq job (for example merge a merge request) and confirm its useragent is unchanged.

Specs:

bin/rspec spec/lib/gitlab/request_context_spec.rb spec/lib/gitlab/middleware/request_context_spec.rb spec/lib/gitlab/tracking/destinations/snowplow_spec.rb

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Merge request reports

Loading
Loading