Skip to main content
Manual tracing gives you complete control over span creation, attributes, and lifecycle. Use it when you need to trace custom operations, add detailed metadata, or track usage and costs.

Getting Started

To start manual tracing, you’ll need to:
  1. Import the required classes from Netra
  2. Create a new span using start_span()
  3. Track your operations within the span
  4. Add relevant attributes and events

Creating Spans

Use start_span() to create a span that wraps a block of code. In Python, use it as a context manager. In TypeScript, explicitly call end() when done.
from netra import Netra

# Use as context manager (recommended)
with Netra.start_span("process-document") as span:
    result = process_document(doc)
    span.set_attribute("document.pages", result.page_count)
    # Span automatically ends when exiting the context

Span Parameters

ParameterTypeDescription
namestringName of the span (required)
attributesdict/objectInitial attributes to set on the span
module_name / moduleNamestringModule or component name for organization
as_type / asTypeSpanTypeType of span (SPAN, GENERATION, TOOL, etc.)
from netra import Netra, SpanType

with Netra.start_span(
    "generate-summary",
    attributes={
        "input.length": len(document),
        "model": "gpt-4",
    },
    module_name="summarization",
    as_type=SpanType.GENERATION,
) as span:
    # Your code here
    pass

Span Types

Use the as_type parameter to categorize spans. This helps Netra display them correctly and enables type-specific features.
TypeUse For
SpanType.GENERATIONLLM completions, image generation
SpanType.EMBEDDINGVector embedding operations
SpanType.TOOLFunction calls, API requests, DB queries
SpanType.AGENTAI agent reasoning and decisions
SpanType.SPANGeneral operations (default)
See Spans for detailed guidance on when to use each type.

Local Span Blocking

You can block specific spans locally within a particular span scope. This is useful when you want to filter out noisy child spans (like HTTP requests) within a specific operation.
from netra import Netra

# Block POST spans within this scope
with Netra.start_span("image-generation", attributes={"blocked_spans": ["POST", "GET"]}) as span:
    # HTTP spans named "POST" or "GET" created within this scope will be filtered
    generate_image(prompt)
This is different from global blocked_spans in Netra.init() which blocks spans across the entire application. Local blocking only affects spans created within the specific parent span’s scope.

SpanWrapper Methods

The start_span() function returns a SpanWrapper object with methods for adding context to your spans.

Setting Span Attributes

Add custom key-value pairs to provide context about the operation:
with Netra.start_span("search-products") as span:
    span.set_attribute("query", user_query)
    span.set_attribute("filters.category", category)
    span.set_attribute("filters.price_range", [min_price, max_price])
    span.set_attribute("results.count", len(results))

LLM-Specific Attributes

For LLM operations, use dedicated methods to set prompts, models, and system information:
from netra import Netra, SpanType

with Netra.start_span("generate-response", as_type=SpanType.GENERATION) as span:
    span.set_prompt(user_message)
    span.set_negative_prompt("blurry, low quality")  # For image generation
    span.set_model("gpt-4-turbo")
    span.set_llm_system("openai")

    response = generate_response(user_message)

    span.set_attribute("completion", response.content)

Recording Events

Track significant moments within a span’s lifecycle:
with Netra.start_span("order-processing") as span:
    span.add_event("validation-started")
    validate_order(order)
    span.add_event("validation-completed", {"valid": True})

    span.add_event("payment-started")
    payment = process_payment(order)
    span.add_event("payment-completed", {
        "transaction_id": payment.id,
        "amount": payment.amount,
    })

Tracking Usage Data

Use UsageModel to track token usage and costs for LLM operations:
from netra import Netra, SpanType, UsageModel

with Netra.start_span("llm-call", as_type=SpanType.GENERATION) as span:
    response = openai.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}],
    )

    # Track usage
    span.set_usage([
        UsageModel(
            model="gpt-4",
            cost_in_usd=calculate_cost(response.usage),
            usage_type="chat",
            units_used=1,
        )
    ])

UsageModel Fields

FieldTypeDescription
modelstringModel name used
cost_in_usd / costInUsdfloatCalculated cost in USD
usage_type / usageTypestringType of usage (e.g., “chat”, “image_generation”)
units_used / unitsUsedintNumber of units consumed

Adding Action Tracking

Use ActionModel to track discrete actions, tool calls, or database operations within a span:
from netra import Netra, ActionModel

with Netra.start_span("agent-execution") as span:
    # Record actions taken by the agent
    span.set_action([
        ActionModel(
            action="DB",
            action_type="INSERT",
            affected_records=[
                {"record_id": "user_123", "record_type": "user"},
                {"record_id": "profile_456", "record_type": "profile"},
            ],
            metadata={
                "table": "users",
                "operation_id": "tx_789",
                "duration_ms": "45",
            },
            success=True,
        ),
        ActionModel(
            action="API",
            action_type="CALL",
            metadata={
                "endpoint": "/api/v1/process",
                "method": "POST",
                "status_code": "200",
            },
            success=True,
        ),
    ])

ActionModel Fields

FieldTypeDescription
actionstringAction category (e.g., “DB”, “API”, “CACHE”)
action_type / actionTypestringAction subtype (e.g., “INSERT”, “SELECT”, “CALL”)
affected_records / affectedRecordsarrayList of affected records with record_id and record_type
metadatadict/objectAdditional metadata as key-value pairs
successbooleanWhether the action succeeded

Error Handling

Mark spans as errors when operations fail:
from netra import Netra

with Netra.start_span("risky-operation") as span:
    try:
        result = risky_operation()
        span.set_success()
    except Exception as e:
        span.set_error(str(e))
        raise
When using Python’s context manager, exceptions are automatically recorded and the span is marked as an error. You can still explicitly call set_error() for custom error messages.

Nested Spans

Create hierarchical traces by nesting spans. Child spans automatically inherit the parent context:
from netra import Netra

def process_order(order: dict):
    with Netra.start_span("process-order") as parent_span:
        parent_span.set_attribute("order.id", order["id"])

        # Child span for validation
        with Netra.start_span("validate-order"):
            validate_order(order)

        # Child span for payment
        with Netra.start_span("process-payment") as payment_span:
            payment = process_payment(order)
            payment_span.set_attribute("payment.id", payment.id)

        # Child span for fulfillment
        with Netra.start_span("fulfill-order"):
            fulfill_order(order)

Accessing the Current Span

Get the currently active span to add attributes from anywhere in your code:
from netra import Netra

def log_user_action(action: str):
    current_span = Netra.get_current_span()
    if current_span:
        current_span.add_event("user-action", {"action": action})

# Usage within a traced operation
with Netra.start_span("user-session"):
    # ... somewhere deep in the call stack ...
    log_user_action("clicked-submit")

Example: RAG Pipeline

This example demonstrates nested spans with multiple span types - a common pattern for AI pipelines.
from netra import Netra, SpanType, UsageModel

def rag_pipeline(query: str):
    with Netra.start_span("rag-pipeline") as pipeline_span:
        pipeline_span.set_attribute("query", query)

        # Step 1: Generate embedding
        with Netra.start_span(
            "generate-embedding", as_type=SpanType.EMBEDDING
        ) as embed_span:
            embedding = embed_model.embed(query)
            embed_span.set_usage([
                UsageModel(
                    model="text-embedding-3-small",
                    usage_type="embedding",
                    units_used=1
                )
            ])

        # Step 2: Retrieve documents
        with Netra.start_span(
            "retrieve-documents", as_type=SpanType.TOOL
        ) as retrieve_span:
            documents = vector_store.search(embedding, top_k=5)
            retrieve_span.set_attribute("documents.count", len(documents))

        # Step 3: Generate response
        with Netra.start_span(
            "generate-response", as_type=SpanType.GENERATION
        ) as generate_span:
            generate_span.set_prompt(query)
            generate_span.set_model("gpt-4")
            generate_span.set_llm_system("openai")

            response = openai.chat.completions.create(
                model="gpt-4",
                messages=[
                    {"role": "system", "content": build_context(documents)},
                    {"role": "user", "content": query},
                ],
            )

            generate_span.set_usage([
                UsageModel(
                    model="gpt-4",
                    cost_in_usd=calculate_cost(response.usage),
                    usage_type="chat",
                    units_used=1,
                )
            ])

        pipeline_span.set_success()
        return response.choices[0].message.content

Best Practices

  1. Use context managers in Python - They ensure spans are properly closed even when exceptions occur.
  2. End spans in TypeScript - Always call span.end() in both success and error paths, preferably in a finally block.
  3. Add meaningful attributes - Include information that will help you debug and analyze traces later.
  4. Track usage for LLM calls - Use setUsage() to monitor token consumption and costs.
  5. Use appropriate span types - Set as_type to categorize spans correctly (GENERATION for LLM calls, TOOL for function calls, etc.).
  6. Handle errors explicitly - Call setError() with descriptive messages to make debugging easier.
  7. Use local span blocking - Filter noisy child spans when you only care about the parent operation.
  8. Add events for milestones - Use addEvent() to mark important points in long-running operations.

Learn More

Last modified on February 2, 2026