> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unomiq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Attaching a Unit to Traces

> Learn how to use the unomiq.unit span attribute to link traces to logical entities like customer IDs, tenant IDs, or agent IDs

Unomiq uses the `unomiq.unit` span attribute to link traces to a **unit** -- a logical entity in your application such as a customer ID, app ID, agent ID, or tenant ID. This allows you to view and filter traces by unit in the Unomiq dashboard or [Unomiq Engine API](/api-reference/engine/query-traces-spans).

You can also optionally set `unomiq.parent_unit` to create a hierarchy of entities, such as linking an app to the organization it belongs to.

This guide shows how to attach the `unomiq.unit` and `unomiq.parent_unit` attributes to your spans.

***

## What is a Unit?

A unit represents the entity that a trace belongs to. Common examples:

| Use case                 | Unit value               |
| ------------------------ | ------------------------ |
| Multi-tenant SaaS        | Tenant ID or customer ID |
| Agentic System           | Agent ID                 |
| Per-user tracking        | User ID                  |
| Per-application tracking | Application ID           |

The value should be a stable identifier that uniquely represents the entity across requests.

You can optionally set `unomiq.parent_unit` alongside `unomiq.unit` to express a parent-child relationship between entities. For example, in a multi-tenant SaaS application you might set `unomiq.unit` to an app ID and `unomiq.parent_unit` to the tenant or organization that app belongs to.

***

## With `unomiq-sdk`

The [`unomiq-sdk`](https://pypi.org/project/unomiq-sdk/) package handles unit attachment automatically alongside OAuth-authenticated OTLP export. Pass `resolve_unit` and `resolve_parent_unit` callables to `Unomiq.init()` and the SDK attaches `unomiq.unit` and `unomiq.parent_unit` to every span.

### Global: Automatic via `resolve_unit` / `resolve_parent_unit`

**Static values:**

```python theme={null}
from unomiq.sdk import Unomiq

Unomiq.init(
    app_name="my-service",
    resolve_unit=lambda: "my-unit-id",
    resolve_parent_unit=lambda: "my-org-id",
    resource_attributes={"service.name": "my-service"},
)
```

**Dynamic per-request (Flask example):**

```python theme={null}
from flask import request
from unomiq.sdk import Unomiq

FALLBACK_UNIT = "default-unit"

def resolve_unit_from_request():
    try:
        # From a path parameter
        customer_id = (request.view_args or {}).get("customer_id")
        if customer_id is not None:
            return str(customer_id)

        # Or from a request header
        return request.headers.get("X-Customer-Id") or FALLBACK_UNIT
    except RuntimeError:
        return FALLBACK_UNIT

Unomiq.init(
    app_name="my-service",
    resolve_unit=resolve_unit_from_request,
    resolve_parent_unit=lambda: "my-org-id",
    resource_attributes={"service.name": "my-service"},
)
```

### Manual: Per-span via `set_unit` / `set_parent_unit`

For full control, skip the global `resolve_unit` config and set attributes directly in route handlers:

```python theme={null}
from opentelemetry import trace
from unomiq.sdk import set_unit, set_parent_unit

@app.route('/customers/<int:customer_id>', methods=['GET'])
def get_customer(customer_id):
    span = trace.get_current_span()
    set_unit(span, str(customer_id))
    set_parent_unit(span, "my-org-id")
    # ... rest of handler
```

### Disabling Unit Tracking

Omit `resolve_unit` (or pass `None`) and the SDK won't attach unit attributes:

```python theme={null}
Unomiq.init(
    app_name="my-service",
    resource_attributes={"service.name": "my-service"},
)
```

***

## Without `unomiq-sdk`

If you are not using `unomiq-sdk`, you can attach unit attributes directly using the OpenTelemetry SDK.

### Setting `unomiq.unit` on Individual Spans

The most straightforward approach is to set the attribute directly on a span:

```python theme={null}
from opentelemetry import trace

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("process-request") as span:
    span.set_attribute("unomiq.unit", user_id)
    span.set_attribute("unomiq.parent_unit", tenant_id)  # optional: parent entity
    # ... your code here
```

`unomiq.parent_unit` is optional. Set it when you want to place the unit within a broader entity hierarchy.

This works with both manual and automatic instrumentation -- as long as you have access to a span, you can set the attribute.

***

### Setting `unomiq.unit` Automatically with a SpanProcessor

To attach the unit to every span without modifying each call site, use a custom `SpanProcessor`. This is useful when the unit is available from a request context (e.g., an HTTP header, JWT claim, or path parameter).

```python theme={null}
from opentelemetry.sdk.trace import SpanProcessor


class UnitSpanProcessor(SpanProcessor):
    """Attaches unomiq.unit to every span from a context resolver."""

    def __init__(self, resolve_unit):
        """
        Args:
            resolve_unit: A callable that returns the current unit value,
                          or None if no unit is available.
        """
        self._resolve_unit = resolve_unit

    def on_start(self, span, parent_context=None):
        unit = self._resolve_unit()
        if unit:
            span.set_attribute("unomiq.unit", unit)

    def on_end(self, span):
        pass

    def shutdown(self):
        pass

    def force_flush(self, timeout_millis=None):
        pass
```

### Example: Resolving Unit from a Flask Request

```python theme={null}
from flask import request, Flask
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider

app = Flask(__name__)


def resolve_unit_from_request():
    """Extract the unit from the current request context."""
    try:
        # Option 1: From a request header
        return request.headers.get("X-Customer-Id")

        # Option 2: From a JWT claim (after auth middleware)
        # return g.current_user.get("tenant_id")

        # Option 3: From a path parameter
        # return request.view_args.get("customer_id")
    except RuntimeError:
        # Outside of a request context
        return None


# Register the processor with the tracer provider
provider = TracerProvider()
provider.add_span_processor(UnitSpanProcessor(resolve_unit_from_request))
trace.set_tracer_provider(provider)
```

### Example: Resolving Unit from Django

```python theme={null}
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider

# Use threading.local or contextvars to store the unit per-request
import contextvars

_current_unit = contextvars.ContextVar("current_unit", default=None)


# In your Django middleware:
class UnitMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        _current_unit.set(request.headers.get("X-Customer-Id"))
        return self.get_response(request)


# Register the processor
provider = TracerProvider()
provider.add_span_processor(UnitSpanProcessor(lambda: _current_unit.get()))
trace.set_tracer_provider(provider)
```

***

## Summary

| Method                              | When to use                                                                       |
| ----------------------------------- | --------------------------------------------------------------------------------- |
| `Unomiq.init()` with `resolve_unit` | Recommended. Handles unit attachment, OAuth, and export in one step               |
| `set_unit()` / `set_parent_unit()`  | Per-span override when using `unomiq-sdk`                                         |
| `span.set_attribute()`              | Without `unomiq-sdk`. One-off spans where you have the unit value at hand         |
| `UnitSpanProcessor`                 | Without `unomiq-sdk`. Automatically attach unit to all spans from request context |

<Note>
  `unomiq.unit` must be set as a **span attribute**. Resource attributes and collector-level processors are not supported for this field.
</Note>
