APM events don’t just represent what happened, but who it happened to and where they were, often revealing performance bottlenecks tied to specific geographies or user demographics.

Here’s a look at a typical APM event, enriched with geo-IP and user context, as it might appear in a system like Datadog or New Relic. Imagine a request to your /api/v1/users/{id} endpoint.

{
  "trace_id": "a1b2c3d4e5f67890",
  "span_id": "fedcba9876543210",
  "name": "GET /api/v1/users/:id",
  "service": "user-api",
  "resource": "GET /api/v1/users/:id",
  "start": 1678886400000,
  "duration": 150,
  "http.status_code": 200,
  "usr.id": "user-12345",
  "usr.email": "alice@example.com",
  "geo.country": "US",
  "geo.city": "New York",
  "geo.region": "NY",
  "geo.ip": "192.0.2.1",
  "usr.segment": "premium",
  "usr.plan": "enterprise"
}

This single event, which might have just shown a 150ms latency for a GET /api/v1/users/:id request, now tells us it was for a premium user (user-12345 from alice@example.com), located in New York, US, and specifically hitting an enterprise plan feature. This level of detail is crucial for understanding performance variations.

The core problem this solves is the "black box" nature of distributed systems. Without this enrichment, you see a slow request, but you don’t know if it’s slow for everyone, just users in a specific region, or a particular customer segment. This leads to inefficient debugging and a poor user experience.

The enrichment process typically happens in two stages:

  1. Geo-IP Lookup: When an incoming request hits your edge or load balancer, its source IP address is looked up against a geo-IP database (like MaxMind GeoLite2, IPinfo, or a commercial offering). This database maps IP ranges to geographical locations (country, region, city) and sometimes even ISP information. This data is then injected into the request’s metadata or directly into the APM agent’s payload.

  2. User Context Injection: When a request is authenticated, your application or an authentication service can extract user identifiers (like user ID, email, roles, subscription tier, or plan type) from session tokens, JWTs, or other authentication mechanisms. This user-specific data is also attached to the request’s metadata or the APM agent’s payload.

The exact levers you control are primarily in how your APM agent is configured and how your application code injects this context.

  • APM Agent Configuration: Most APM agents (e.g., Datadog, New Relic, Elastic APM) have settings to automatically capture certain request attributes. For geo-IP, you often configure an "IP enrichment" or "geo-tagging" feature. For user context, you’ll typically use agent APIs to manually set custom tags or attributes within your application code. For example, in Datadog’s Python agent, you might use ddtrace.tracer.set_tag("usr.id", user_id).

  • Application Logic: Your application code is responsible for obtaining the geo-IP and user context to pass to the APM agent. This might involve:

    • Accessing the X-Forwarded-For or Remote-Addr header for the IP address.
    • Calling an external geo-IP service or using a local database.
    • Extracting user details from an authenticated session or token.
    • Looking up user details (like plan type) from a user database based on the authenticated user ID.

The most surprising part of this process is how deeply intertwined the "network" and "application" layers become within APM data. When you see a geo-tag like geo.country: "CN", it’s not just a label; it’s a direct output of your ingress network stack’s IP lookup combined with your application’s APM agent understanding that this network-level data is relevant to the application-level transaction it’s tracing. The agent doesn’t inherently know about geography; it’s instructed to capture and attach specific metadata that you provide, which happens to be derived from network information.

Understanding how these tags are propagated and how different services in your trace might add or overwrite them is the next crucial step in mastering distributed tracing.

Want structured learning?

Take the full Elastic-apm course →