Skip to main content

When a rule “should have fired” or a session update looks wrong, guessing from logs is slow. Unomi 3 adds query-parameter tracing ( UNOMI-873 / #775): append explain=true and the response includes a requestTracing tree.

explain=true producing a requestTracing tree of conditions and actions

Where it works

  • POST/GET /cxs/context.json and /cxs/context.js
  • POST/GET /cxs/eventcollector

Capabilities

  • Opt-in per request?explain=true enables tracing for that call only
  • Response fieldrequestTracing on ContextResponse / EventCollectorResponse
  • Hierarchical tree — nested TraceNode children for nested work
  • Per-node fieldsoperationType, description, context, result, startTime, endTime, traces[], children[]
  • What gets traced — request processing, event validation, rule evaluation, condition evaluation (property/boolean/…), action execution (visit counters, segment eval, age, …), personalization / content-filter evaluation on context requests
  • Timings — wall-clock start/end on every node for latency hunting
  • Auth gateROLE_UNOMI_ADMIN / ROLE_UNOMI_TENANT_ADMIN only; public API key alone returns HTTP 403
  • Low overhead when off — tracer disabled unless explain=true; reset after the request
  • Programmatic APITracerService / RequestTracer for services that start nested operations
  • IT coverage — authorized explain success + unauthorized 403 paths

Auth: tenant admin, not the public key

Tracing is restricted to ADMINISTRATOR / TENANT_ADMINISTRATOR. In practice that means authenticate with the tenant private API key (Basic tenantId:privateApiKey), not the public track key.

We verified the following against a local Unomi 3.1.0-SNAPSHOT (HTTPS). Public-key + explain=true returned HTTP 403 with:

{"errorMessage":"Insufficient privileges to access tracing information"}

Integration coverage: EventsCollectorIT#testEventsCollectorWithExplain and #testEventsCollectorWithExplainUnauthorized.

Prerequisite: create the scope

JSON Schema validation rejects events whose scope is unknown for the tenant (Unknown scope value … for value myscope). Create the scope first:

curl -X POST http://localhost:8181/cxs/scopes \
  -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "itemId": "myscope",
    "itemType": "scope",
    "metadata": { "id": "myscope", "name": "My scope" }
  }'

Example: event collector

Include target.properties.pageInfo (at least referringURL / pagePath). Built-in rules such as sessionReferrer read those fields; omitting them still works but logs noisy WARNs.

curl -X POST 'http://localhost:8181/cxs/eventcollector?explain=true' \
  -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \
  -H 'Content-Type: application/json' \
  -d @- <<'EOF'
{
  "sessionId": "trace-demo-1",
  "events": [
    {
      "eventType": "view",
      "scope": "myscope",
      "source": {
        "itemType": "site",
        "scope": "myscope",
        "itemId": "mysite"
      },
      "target": {
        "itemType": "page",
        "scope": "myscope",
        "itemId": "homepage",
        "properties": {
          "pageInfo": {
            "pageID": "homepage",
            "pagePath": "/home",
            "pageName": "Home",
            "destinationURL": "https://example.com/home",
            "referringURL": "https://example.com/",
            "language": "en"
          }
        }
      }
    }
  ]
}
EOF

Verified excerpt (trimmed) from a live 3.1 response — session create, condition checks, visit counters:

{
  "updated": 6,
  "requestTracing": {
    "operationType": "event-collection",
    "description": "Event collection request processed successfully",
    "startTime": 1784099217601,
    "endTime": 1784099217629,
    "traces": [
      "Sending event: sessionCreated - Context: …",
      "Processing event for profile: …"
    ],
    "children": [
      {
        "operationType": "condition-evaluation",
        "description": "Condition evaluation completed",
        "result": "true",
        "children": [
          {
            "operationType": "boolean",
            "description": "OR condition succeeded on subcondition",
            "result": "true"
          }
        ]
      },
      {
        "operationType": "action-execution",
        "description": "Action execution completed",
        "children": [
          {
            "operationType": "increment-property",
            "description": "Property incremented successfully",
            "traces": [
              "Processing property increment - Context: {propertyName=nbOfVisits, …}",
              "Property increment result - Context: {newValue=1, isUpdated=true}"
            ]
          }
        ]
      }
    ]
  }
}

Example: context.json

curl -X POST \
  'http://localhost:8181/cxs/context.json?sessionId=trace-demo-1&explain=true' \
  -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \
  -H 'Content-Type: application/json' \
  -d @- <<'EOF'
{
  "source": {
    "itemId": "homepage",
    "itemType": "page",
    "scope": "myscope"
  },
  "requiredProfileProperties": ["*"],
  "requireSegments": true
}
EOF

Live trace root on the same stack:

"requestTracing": {
  "operationType": "context-request",
  "description": "Context request processed successfully",
  "children": [
    { "operationType": "condition-evaluation", … },
    { "operationType": "action-execution",
      "children": [
        { "operationType": "evaluate-age", "description": "No changes needed" },
        { "operationType": "evaluate-segments", "description": "No changes needed" }
      ]
    }
  ]
}

How to read the tree

  • Timings — subtract endTime - startTime on hot nodes when hunting latency
  • Conditions — look for result: false on rules you expected to match
  • Actionsincrement-property, evaluate-segments, visit helpers show whether profile state actually changed

For save-time condition checks (before runtime), use condition validation in the manual; tracing is the runtime companion.

Manual: explain / tracing Manual improvements Task scheduler

← All posts