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

# Custom Spans

> Create custom spans for business logic tracing and workflow observability

<Tip>
  **Decorator-First:** Use `@trace` decorators as your primary pattern. Context managers are for special cases like loops or conditional tracing.
</Tip>

## Overview

Custom spans let you trace specific business logic, workflow steps, and application components beyond just LLM calls.

**Use Cases:**

* Business process tracking
* Performance bottleneck identification
* Complex workflow visualization
* Custom error tracking

***

## The `@trace` Decorator

The recommended approach for function-level tracing:

```python theme={null}
import os
from honeyhive import HoneyHiveTracer, trace, enrich_span

tracer = HoneyHiveTracer.init(
    api_key=os.getenv("HH_API_KEY"),
    project=os.getenv("HH_PROJECT")
)

@trace
def process_request(user_id: str, data: dict) -> dict:
    """Automatically traced with inputs/outputs captured."""
    
    # Add custom context (see Enriching Traces for patterns)
    enrich_span({"user_id": user_id})
    
    result = do_processing(data)
    return {"status": "success", "data": result}

@trace
def nested_workflow(request: dict) -> dict:
    """Nested calls create trace hierarchy automatically."""
    validated = validate(request)      # Child span
    processed = process(validated)     # Child span
    return save(processed)             # Child span
```

**Benefits:**

* ✅ Automatic inputs/outputs capture
* ✅ Nested calls create proper trace hierarchy
* ✅ Clean code without span management clutter

<Note>
  For details on adding metadata with `enrich_span()`, see [Enriching Traces](/v2/tracing/enrich-traces).
</Note>

***

## Async Functions

The `@trace` decorator works with both sync and async functions automatically:

```python theme={null}
@trace
async def fetch_data(url: str) -> dict:
    """Async function - @trace works automatically."""
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.json()

# Call with await
result = await fetch_data("https://api.example.com/data")
```

<Note>
  **No separate `@atrace` needed.** The decorator detects async functions automatically.
</Note>

***

## Context Managers

Use context managers for scenarios where decorators don't fit:

### When to Use

* ✅ **Loop iterations** - Tracing individual items in batch processing
* ✅ **Conditional spans** - Dynamic span creation based on runtime conditions
* ✅ **Non-function blocks** - Setup, cleanup, or configuration phases
* ❌ **Regular functions** - Use `@trace` instead

### `enrich_span_context()` (Recommended)

Creates spans with automatic HoneyHive namespacing:

```python theme={null}
from honeyhive.tracer.processing.context import enrich_span_context

@trace
def process_batch(items: list) -> list:
    results = []
    
    for i, item in enumerate(items):
        with enrich_span_context(
            event_name=f"process_item_{i}",
            inputs={"item": item},
            metadata={"batch_size": len(items)}
        ):
            result = transform_item(item)
            tracer.enrich_span(outputs={"result": result})
            results.append(result)
    
    return results
```

### `tracer.start_span()` (Low-Level)

For raw OpenTelemetry-style control:

```python theme={null}
with tracer.start_span("process_item") as span:
    span.set_attribute("item.index", i)
    result = do_processing()
    span.set_attribute("success", True)
```

### Comparison

| Feature              | `enrich_span_context()` | `tracer.start_span()` |
| -------------------- | ----------------------- | --------------------- |
| Auto namespacing     | ✅ Automatic             | ❌ Manual              |
| HoneyHive enrichment | ✅ Built-in              | ❌ Manual attributes   |
| Best for             | Business logic          | Low-level control     |

***

## Conditional Spans

Create spans only when conditions are met:

```python theme={null}
import os

DEBUG_MODE = os.getenv("DEBUG", "false").lower() == "true"

@trace
def operation_with_debug(data: dict):
    if DEBUG_MODE:
        with enrich_span_context(
            event_name="debug_inspection",
            inputs={"data": data}
        ):
            inspect_data(data)
    
    return process(data)
```

***

## Best Practices

### Span Naming

```python theme={null}
# ✅ Good: Descriptive, hierarchical
@trace(event_name="user_authentication")
@trace(event_name="payment_processing_stripe")

# ❌ Bad: Generic
@trace(event_name="process")
@trace(event_name="api_call")
```

### Avoid Over-Instrumentation

```python theme={null}
# ❌ Bad: Span per item in hot path
for item in million_items:
    with enrich_span_context(event_name="process_item"):
        process(item)

# ✅ Good: Batch-level span only
@trace
def process_batch(items: list):
    for item in items:
        process(item)
```

***

## Related

<CardGroup cols={2}>
  <Card title="Enriching Traces" icon="sparkles" href="/v2/tracing/enrich-traces">
    Add metadata with enrich\_span and enrich\_session
  </Card>

  <Card title="Enrichment Schema" icon="book" href="/v2/tracing/enrichment-schema">
    Namespaces, data types, backend attributes
  </Card>

  <Card title="Distributed Tracing" icon="network-wired" href="/v2/tracing/distributed-tracing">
    Cross-service tracing
  </Card>

  <Card title="Python SDK Reference" icon="code" href="https://honeyhiveai.github.io/python-sdk/">
    Full API documentation
  </Card>
</CardGroup>
