HoneyHiveTracer API Reference
=============================

.. note::
   **Complete API documentation for the HoneyHiveTracer class**
   
   The primary interface for tracing LLM operations and custom application logic with HoneyHive observability.

.. important::
   **🆕 NEW: Modular Architecture & Hybrid Configuration**
   
   The ``HoneyHiveTracer`` has been completely rewritten with a modular, mixin-based architecture and now supports Pydantic configuration models.
   
   **See Also:**
   
   - :doc:`tracer-architecture` - Detailed architectural information
   - :doc:`config-models` - Complete configuration models API reference

.. currentmodule:: honeyhive

.. autoclass:: HoneyHiveTracer
   :members:
   :undoc-members:
   :show-inheritance:

The ``HoneyHiveTracer`` is the core component of the HoneyHive SDK, providing OpenTelemetry-based tracing with LLM-specific optimizations and BYOI (Bring Your Own Instrumentor) architecture support.

**🆕 Architecture Overview:**

The tracer is now composed from multiple mixins using dynamic inheritance:

.. code-block:: python

   class HoneyHiveTracer(HoneyHiveTracerBase, TracerOperationsMixin, TracerContextMixin):
       """Main tracer class composed from multiple mixins."""

**Modular Components:**

- **HoneyHiveTracerBase**: Core initialization and configuration (``tracer/core/base.py``)
- **TracerOperationsMixin**: Span creation and event management (``tracer/core/operations.py``)
- **TracerContextMixin**: Context and baggage management (``tracer/core/context.py``)

**Key Features:**

- **🆕 Hybrid Configuration**: Supports both Pydantic config objects and traditional parameters
- **🆕 Modular Architecture**: Mixin-based composition with 35 files across 6 modules
- Multi-instance support for different projects/environments
- Automatic OpenTelemetry configuration and management  
- LLM-specific span attributes and conventions
- Graceful degradation and error handling
- Built-in instrumentor management
- Thread-safe operations
- Context propagation across async/threaded operations

**🆕 Configuration Options:**

The tracer supports three initialization patterns:

.. tabs::

   .. tab:: 🆕 Modern Config Objects (Recommended)

      .. code-block:: python

         from honeyhive import HoneyHiveTracer
         from honeyhive.config.models import TracerConfig
         
         config = TracerConfig(
             api_key="hh_1234567890abcdef",
             project="my-llm-project",
             verbose=True
         )
         tracer = HoneyHiveTracer(config=config)

   .. tab:: 🔄 Traditional Parameters (Backwards Compatible)

      .. code-block:: python

         from honeyhive import HoneyHiveTracer
         
         tracer = HoneyHiveTracer(
             api_key="hh_1234567890abcdef",
             project="my-llm-project",
             verbose=True
         )

   .. tab:: 🔀 Mixed Approach

      .. code-block:: python

         from honeyhive import HoneyHiveTracer
         from honeyhive.config.models import TracerConfig
         
         config = TracerConfig(api_key="hh_1234567890abcdef", project="my-llm-project")
         tracer = HoneyHiveTracer(config=config, verbose=True)  # verbose overrides config

Class Methods
-------------

init()
~~~~~~

.. py:classmethod:: HoneyHiveTracer.init(config: Optional[TracerConfig] = None, session_config: Optional[SessionConfig] = None, evaluation_config: Optional[EvaluationConfig] = None, **kwargs) -> "HoneyHiveTracer"
   :no-index:

   Initialize a new HoneyHiveTracer instance with the specified configuration.

   All parameters can be passed as keyword arguments (``**kwargs``) directly, or grouped
   into Pydantic config objects for more structured configuration.

   **Common kwargs:**

   :param api_key: HoneyHive API key. If not provided, reads from ``HH_API_KEY`` environment variable.
   :type api_key: Optional[str]

   :param project: Project name. If not provided, reads from ``HH_PROJECT`` environment variable.
   :type project: Optional[str]

   :param session_name: Custom session name for grouping related traces. Auto-generated if not provided.
   :type session_name: Optional[str]

   :param source: Source environment identifier (e.g., ``"production"``, ``"staging"``, ``"development"``). Defaults to ``"dev"``.
   :type source: str

   :param server_url: Custom HoneyHive server URL for self-hosted deployments. Overrides ``HH_API_URL`` environment variable.
   :type server_url: Optional[str]

   :param session_id: Existing session ID to link to (must be a valid UUID string).
   :type session_id: Optional[str]

   :param disable_http_tracing: Whether to disable HTTP request tracing. Defaults to ``True`` for performance.
   :type disable_http_tracing: bool

   :param verbose: Enable verbose debug logging. Defaults to ``False``.
   :type verbose: bool

   :param test_mode: Enable test mode — no data sent to HoneyHive. Defaults to ``False``.
   :type test_mode: bool

   :param config: Pydantic ``TracerConfig`` object for structured configuration.
   :type config: Optional[TracerConfig]

   :param session_config: Pydantic ``SessionConfig`` object for session-specific configuration.
   :type session_config: Optional[SessionConfig]

   :param evaluation_config: Pydantic ``EvaluationConfig`` object for evaluation-specific configuration.
   :type evaluation_config: Optional[EvaluationConfig]
   
   **Returns:**
   
   :rtype: HoneyHiveTracer
   :returns: Configured HoneyHiveTracer instance
   
   **Raises:**
   
   :raises ValueError: If required configuration is missing or invalid
   :raises ConnectionError: If unable to connect to HoneyHive API
   :raises ImportError: If required dependencies are missing
   
   **Environment Variable Priority:**
   
   The ``init()`` method respects environment variables with the following precedence:
   
   1. Explicit parameters (highest priority)
   2. Environment variables
   3. Default values (lowest priority)
   
   **Supported Environment Variables:**
   
   .. list-table::
      :header-rows: 1
      :widths: 25 45 30
      
      * - Variable
        - Description
        - Default
      * - ``HH_API_KEY``
        - HoneyHive API key
        - **Required**
      * - ``HH_PROJECT``
        - Project identifier
        - **Required**
      * - ``HH_SOURCE``
        - Source identifier
        - "production"
      * - ``HH_API_URL``
        - Custom server URL
        - "https://api.honeyhive.ai"
      * - ``HH_TEST_MODE``
        - Enable test mode
        - "false"
      * - ``HH_DISABLE_HTTP_TRACING``
        - Disable HTTP tracing
        - "true"
   
   **Basic Usage Examples:**
   
   .. code-block:: python
   
      from honeyhive import HoneyHiveTracer
      
      # Minimal setup (uses environment variables)
      # Requires HH_API_KEY and HH_PROJECT environment variables to be set
      tracer = HoneyHiveTracer.init()
      
      # Or specify project explicitly
      tracer = HoneyHiveTracer.init(
          project="your-project"  # Or set HH_PROJECT environment variable
      )
      
      # Explicit configuration
      tracer = HoneyHiveTracer.init(
          api_key="hh_your_api_key_here",  # Or set HH_API_KEY environment variable
          project="your-project",          # Or set HH_PROJECT environment variable
          source="production"              # Or set HH_SOURCE environment variable
      )
      
      # Development mode
      tracer = HoneyHiveTracer.init(
          api_key="hh_dev_key",            # Or set HH_API_KEY environment variable
          project="your-project",          # Or set HH_PROJECT environment variable
          source="development",            # Or set HH_SOURCE environment variable
          test_mode=True                   # No data sent to HoneyHive (or set HH_TEST_MODE=true)
      )
   
   **BYOI (Bring Your Own Instrumentor) Pattern:**
   
   .. code-block:: python
   
      from openinference.instrumentation.openai import OpenAIInstrumentor
      from openinference.instrumentation.anthropic import AnthropicInstrumentor
      
      # Single instrumentor
      # Step 1: Initialize HoneyHive tracer first (without instrumentors)
      tracer = HoneyHiveTracer.init(
          api_key="your-api-key",  # Or set HH_API_KEY environment variable
          project="your-project"   # Or set HH_PROJECT environment variable
      )
      
      # Step 2: Initialize instrumentor separately with tracer_provider
      instrumentor = OpenAIInstrumentor()
      instrumentor.instrument(tracer_provider=tracer.provider)
      
      # Multiple instrumentors for multi-LLM applications
      # Step 1: Initialize HoneyHive tracer first (without instrumentors)
      tracer = HoneyHiveTracer.init(
          api_key="your-api-key",  # Or set HH_API_KEY environment variable
          project="your-project"   # Or set HH_PROJECT environment variable
      )
      
      # Step 2: Initialize instrumentors separately with tracer_provider
      openai_instrumentor = OpenAIInstrumentor()
      anthropic_instrumentor = AnthropicInstrumentor()
      
      openai_instrumentor.instrument(tracer_provider=tracer.provider)
      anthropic_instrumentor.instrument(tracer_provider=tracer.provider)

**Multi-Instance Examples:**

   .. note::
      **Multi-Instance Pattern**: Each tracer instance requires a unique ``api_key`` + ``project`` pair to properly target different HoneyHive projects. For same project across environments, use the same API key but different ``source`` values.
      
      **Environment Variable Limitation**: Standard ``HH_API_KEY`` and ``HH_PROJECT`` environment variables are global per process and don't work for multi-project scenarios. Use explicit parameters or custom environment variables for each service.
   
   .. code-block:: python
   
      # Different projects - MUST use explicit parameters (not HH_* env vars)
      user_tracer = HoneyHiveTracer.init(
          api_key="hh_user_service_key",    # Unique API key for user-service project
          project="user-service",           # Target project: user-service
          source="production"               # Explicit source (HH_SOURCE won't work for multi-instance)
      )
      
      payment_tracer = HoneyHiveTracer.init(
          api_key="hh_payment_service_key", # Unique API key for payment-service project  
          project="payment-service",        # Target project: payment-service
          source="production"               # Explicit source (HH_SOURCE won't work for multi-instance)
      )
      
      # Different environments - same project (can use HH_* env vars OR explicit params)
      # Option 1: Explicit parameters (recommended for clarity)
      prod_tracer = HoneyHiveTracer.init(
          api_key="hh_my_project_key",     # Same API key for same project
          project="my-project",            # Same target project
          source="production"              # Different environment
      )
      
      staging_tracer = HoneyHiveTracer.init(
          api_key="hh_my_project_key",     # Same API key for same project
          project="my-project",            # Same target project  
          source="staging"                 # Different environment
      )
      
      # Option 2: Environment variables (works for single project only)
      # export HH_API_KEY="hh_my_project_key"
      # export HH_PROJECT="my-project"
      dev_tracer = HoneyHiveTracer.init(
          source="development",            # Only source differs
          test_mode=True                   # Enable test mode for development
      )
      
      # Option 3: Custom environment variables for multi-project (recommended pattern)
      # Use service-specific environment variables instead of global HH_* vars:
      # export USER_SERVICE_API_KEY="hh_user_service_key"
      # export USER_SERVICE_PROJECT="user-service"
      # export PAYMENT_SERVICE_API_KEY="hh_payment_service_key"  
      # export PAYMENT_SERVICE_PROJECT="payment-service"
      
      import os
      user_tracer = HoneyHiveTracer.init(
          api_key=os.getenv("USER_SERVICE_API_KEY"),      # Service-specific env var
          project=os.getenv("USER_SERVICE_PROJECT"),      # Service-specific env var
          source="production"
      )
      
      payment_tracer = HoneyHiveTracer.init(
          api_key=os.getenv("PAYMENT_SERVICE_API_KEY"),   # Service-specific env var
          project=os.getenv("PAYMENT_SERVICE_PROJECT"),   # Service-specific env var
          source="production"
      )
   
   **Self-Hosted Deployment:**
   
   .. code-block:: python
   
      # Custom HoneyHive deployment
      tracer = HoneyHiveTracer.init(
          api_key="hh_your_key",                      # Or set HH_API_KEY environment variable
          project="your-project",                     # Or set HH_PROJECT environment variable
          server_url="https://honeyhive.company.com"  # Or set HH_API_URL environment variable
      )
   
   **Backwards Compatibility Examples (v0.1.0rc2+):**
   
   All 16 original parameters from the main branch are now supported:
   
   .. code-block:: python
   
      from honeyhive import HoneyHiveTracer
      
      # Full backwards compatibility - all original parameters work
      tracer = HoneyHiveTracer.init(
          api_key="hh_your_key",                   # Or set HH_API_KEY environment variable
          project="my-project",                    # Required parameter (or set HH_PROJECT)
          session_name="evaluation-session",
          source="production",
          server_url="https://custom.honeyhive.ai", # Overrides HH_API_URL
          session_id="550e8400-e29b-41d4-a716-446655440000", # Valid UUID
          disable_http_tracing=True,               # Default for performance
          disable_batch=False,                     # Use BatchSpanProcessor (default)
          verbose=True,                            # Enable debug output
          inputs={"user_id": "123", "query": "test"}, # Session inputs
          is_evaluation=True,                      # Evaluation workflow
          run_id="eval-run-001",                   # Evaluation run
          dataset_id="dataset-123",                # Evaluation dataset
          datapoint_id="datapoint-456",            # Evaluation datapoint
          test_mode=False                          # Send data to HoneyHive
      )
      
      # Evaluation workflow example
      evaluation_tracer = HoneyHiveTracer.init(
          api_key="hh_eval_key",           # Or set HH_API_KEY environment variable
          project="evaluation-project",    # Or set HH_PROJECT environment variable
          is_evaluation=True,
          run_id="experiment-2024-001",
          dataset_id="benchmark-dataset",
          verbose=True  # See evaluation baggage being set
      )
      
      # Context propagation example
      parent_carrier = {"traceparent": "00-trace-id-span-id-01"}
      child_tracer = HoneyHiveTracer.init(
          api_key="hh_key",                # Or set HH_API_KEY environment variable
          project="your-project",          # Or set HH_PROJECT environment variable
          link_carrier=parent_carrier,     # Links to parent trace
          verbose=True
      )
      
      # Performance tuning example
      high_throughput_tracer = HoneyHiveTracer.init(
          api_key="hh_key",                # Or set HH_API_KEY environment variable
          project="your-project",          # Or set HH_PROJECT environment variable
          disable_batch=True,              # Use SimpleSpanProcessor for immediate export
          disable_http_tracing=True,       # Reduce overhead (or set HH_DISABLE_HTTP_TRACING=true)
          verbose=False                    # Minimal logging
      )

Constructor
-----------

__init__()
~~~~~~~~~~

.. automethod:: HoneyHiveTracer.__init__

   Direct constructor method. Generally prefer using the ``init()`` class method for initialization.

Instance Methods
----------------

trace()
~~~~~~~

.. py:method:: trace(name: str, event_type: Optional[str] = None, **kwargs) -> ContextManager[Span]
   :no-index:

   Create a traced span as a context manager for manual instrumentation.
   
   **Parameters:**
   
   :param name: Human-readable name for the operation being traced
   :type name: str
   
   :param event_type: Event type for categorization. Must be one of: ``"model"``, ``"tool"``, or ``"chain"``
   :type event_type: Optional[str]
   
   :param kwargs: Additional span attributes to set on creation
   :type kwargs: Any
   
   **Returns:**
   
   :rtype: ContextManager[opentelemetry.trace.Span]
   :returns: Context manager yielding an OpenTelemetry Span object
   
   **Automatic Span Attributes:**
   
   The span automatically includes HoneyHive-specific attributes:
   
   - ``honeyhive.project``: Project name
   - ``honeyhive.source``: Source identifier  
   - ``honeyhive.session_name``: Session name
   - ``honeyhive.tracer_version``: SDK version
   - ``honeyhive.event_type``: Event type (if provided)
   
   **Basic Usage:**
   
   .. code-block:: python
   
      # Simple operation tracing
      with tracer.trace("user_lookup") as span:
          user = get_user_by_id(user_id)
          span.set_attribute("user.id", user_id)
          span.set_attribute("user.found", user is not None)
      
      # With custom event type
      with tracer.trace("llm_completion", event_type="openai_gpt4") as span:
          response = openai_client.chat.completions.create(
              model="gpt-4",
              messages=[{"role": "user", "content": prompt}]
          )
          span.set_attribute("model", "gpt-4")
          span.set_attribute("prompt.length", len(prompt))
          span.set_attribute("response.length", len(response.choices[0].message.content))
      
      # With initial attributes
      with tracer.trace("data_processing", 
                       operation_type="batch",
                       batch_size=100) as span:
          result = process_batch(data)
          span.set_attribute("processing.success", True)
   
   **Nested Spans (Automatic Context Propagation):**
   
   .. code-block:: python
   
      # Parent-child span relationships are automatic
      with tracer.trace("parent_operation") as parent:
          parent.set_attribute("operation.level", "parent")
          
          # Child spans inherit trace context
          with tracer.trace("child_operation") as child:
              child.set_attribute("operation.level", "child")
              
              # Grandchild spans
              with tracer.trace("grandchild_operation") as grandchild:
                  grandchild.set_attribute("operation.level", "grandchild")
   
   **Error Handling and Status:**
   
   .. code-block:: python
   
      from opentelemetry import trace
      
      with tracer.trace("risky_operation") as span:
          try:
              result = risky_function()
              span.set_status(trace.Status(trace.StatusCode.OK))
              span.set_attribute("operation.success", True)
          except ValueError as e:
              span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
              span.record_exception(e)
              span.set_attribute("operation.success", False)
              span.set_attribute("error.type", "ValueError")
              raise
          except Exception as e:
              span.set_status(trace.Status(trace.StatusCode.ERROR, "Unexpected error"))
              span.record_exception(e)
              span.set_attribute("operation.success", False)
              span.set_attribute("error.type", type(e).__name__)
              raise
   
   **Performance Measurement:**
   
   .. code-block:: python
   
      import time
      
      with tracer.trace("performance_critical_operation") as span:
          start_time = time.perf_counter()
          
          # Your operation here
          result = expensive_computation()
          
          duration = time.perf_counter() - start_time
          span.set_attribute("performance.duration_seconds", duration)
          span.set_attribute("performance.operations_per_second", 1 / duration)

enrich_current_span()
~~~~~~~~~~~~~~~~~~~~~

.. py:method:: enrich_current_span(attributes: Dict[str, Any]) -> None

   Add attributes to the currently active span without needing direct span reference.
   
   **Parameters:**
   
   :param attributes: Dictionary of attributes to add to the current span
   :type attributes: Dict[str, Any]
   
   **Usage:**
   
   This method is particularly useful when using the ``@trace`` decorator where you don't have direct access to the span object.
   
   .. code-block:: python
   
      from honeyhive import trace
      
      @trace(tracer=tracer, event_type="user_processing")
      def process_user_request(user_id: str, request_data: dict):
          # Add attributes to the automatically created span
          tracer.enrich_current_span({
              "user.id": user_id,
              "user.tier": get_user_tier(user_id),
              "request.size": len(str(request_data)),
              "request.type": request_data.get("type", "unknown"),
              "request.timestamp": time.time()
          })
          
          # Continue processing...
          result = process_request(request_data)
          
          # Add more attributes based on results
          tracer.enrich_current_span({
              "response.success": True,
              "response.size": len(str(result)),
              "processing.duration": time.time() - start_time
          })
          
          return result
      
      # In a nested function without decorator
      def helper_function(data):
          # This will add to the active span from the parent function
          tracer.enrich_current_span({
              "helper.input_size": len(data),
              "helper.processing_method": "optimized"
          })
          return processed_data
   
   **Conditional Enrichment:**
   
   .. code-block:: python
   
      @trace(tracer=tracer)
      def conditional_processing(user_id: str, options: dict):
          # Always add basic info
          tracer.enrich_current_span({
              "user.id": user_id,
              "options.provided": len(options)
          })
          
          # Conditionally add detailed info for premium users
          user_tier = get_user_tier(user_id)
          if user_tier == "premium":
              tracer.enrich_current_span({
                  "user.tier": user_tier,
                  "user.detailed_options": str(options),
                  "processing.enhanced": True
              })

flush()
~~~~~~~

.. py:method:: flush(timeout: Optional[float] = None) -> bool

   Force immediate export of all pending trace data to HoneyHive.
   
   **Parameters:**
   
   :param timeout: Maximum time to wait for flush completion in seconds. If None, uses default timeout.
   :type timeout: Optional[float]
   
   **Returns:**
   
   :rtype: bool
   :returns: True if flush completed successfully within timeout, False otherwise
   
   **Usage:**
   
   .. code-block:: python
   
      # Before application shutdown
      print("Flushing traces before exit...")
      success = tracer.flush(timeout=10.0)
      if success:
          print("All traces sent successfully")
      else:
          print("Warning: Some traces may not have been sent")
      
      # In exception handlers
      try:
          main_application_logic()
      except KeyboardInterrupt:
          print("Received interrupt, flushing traces...")
          tracer.flush(timeout=5.0)
          raise
      
      # Periodic flushing in long-running applications
      import time
      import threading
      
      def periodic_flush():
          while True:
              time.sleep(60)  # Flush every minute
              success = tracer.flush(timeout=30.0)
              if not success:
                  logger.warning("Periodic flush failed")
      
      # Start background flush thread
      flush_thread = threading.Thread(target=periodic_flush, daemon=True)
      flush_thread.start()

close()
~~~~~~~

.. py:method:: close() -> None

   Gracefully shutdown the tracer and release all resources.
   
   **Usage:**
   
   .. code-block:: python
   
      # Clean shutdown sequence
      try:
          # First flush any pending traces
          tracer.flush(timeout=10.0)
      finally:
          # Then close the tracer
          tracer.close()
      
      # Using context manager for automatic cleanup
      with HoneyHiveTracer.init(
          api_key="hh_key",        # Or set HH_API_KEY environment variable
          project="your-project"   # Or set HH_PROJECT environment variable
      ) as tracer:
          # Use tracer for operations
          with tracer.trace("operation"):
              do_work()
      # Tracer automatically flushed and closed here
      
      # In application cleanup handlers
      import atexit
      
      tracer = HoneyHiveTracer.init(
          api_key="hh_key",        # Or set HH_API_KEY environment variable
          project="your-project"   # Or set HH_PROJECT environment variable
      )
      
      def cleanup_tracer():
          print("Cleaning up tracer...")
          tracer.flush(timeout=5.0)
          tracer.close()
      
      atexit.register(cleanup_tracer)

Session Management Methods
--------------------------

create_session()
~~~~~~~~~~~~~~~~

.. py:method:: create_session(session_name: Optional[str] = None, session_id: Optional[str] = None, inputs: Optional[Dict[str, Any]] = None, metadata: Optional[Dict[str, Any]] = None, user_properties: Optional[Dict[str, Any]] = None, source: Optional[str] = None, skip_api_call: bool = False) -> Optional[str]
   :no-index:

   Create a new session and set it in the current request context using OpenTelemetry baggage.
   
   **🆕 Recommended for Web Servers:** This method stores the session_id in OpenTelemetry baggage
   (which uses Python's ``ContextVar`` internally), providing proper request-scoped isolation.
   It does NOT modify the tracer instance's session_id, making it safe for concurrent requests.
   
   **Parameters:**
   
   :param session_name: Human-readable name for the session. Auto-generated if not provided.
   :type session_name: Optional[str]
   
   :param session_id: Custom session ID. By default, the API is called with this ID to create
                      the session in the backend. If ``skip_api_call=True``, just sets this ID
                      in baggage without making an API call (for linking to existing sessions).
   :type session_id: Optional[str]
   
   :param inputs: Input data for the session (e.g., user query, request data).
   :type inputs: Optional[Dict[str, Any]]
   
   :param metadata: Additional metadata for the session.
   :type metadata: Optional[Dict[str, Any]]
   
   :param user_properties: User-specific properties (user_id, plan, etc.).
   :type user_properties: Optional[Dict[str, Any]]
   
   :param source: Source environment override. Uses tracer's source if not provided.
   :type source: Optional[str]
   
   :param skip_api_call: If True and session_id is provided, skip the API call and just
                         set session_id in baggage. Useful for linking to sessions that
                         were already created externally. Defaults to False.
   :type skip_api_call: bool
   
   **Returns:**
   
   :rtype: Optional[str]
   :returns: Session ID if successful, None otherwise
   
   **Basic Usage (Flask/Django):**
   
   .. code-block:: python
   
      from flask import Flask, request, g
      from honeyhive import HoneyHiveTracer, trace
      
      app = Flask(__name__)
      tracer = HoneyHiveTracer.init(api_key="...", project="my-api")
      
      @app.before_request
      def create_session_for_request():
          """Create session before each request."""
          g.session_id = tracer.create_session(
              session_name=f"flask-{request.path}",
              inputs={"method": request.method, "path": request.path},
              user_properties={"user_id": request.headers.get("X-User-ID")}
          )
      
      @app.after_request
      def enrich_session_after_request(response):
          """Enrich session with response data."""
          tracer.enrich_session(outputs={"status_code": response.status_code})
          return response
   
   **Custom Session ID (API creates session with your ID):**
   
   .. code-block:: python
   
      # Create session with your own ID via API
      my_session_id = f"user-{user_id}-{timestamp}"
      tracer.create_session(
          session_id=my_session_id,  # API creates session with this ID
          session_name="user-session",
          inputs={"user_id": user_id}
      )
   
   **Link to Existing Session (no API call):**
   
   .. code-block:: python
   
      # Link to session that was already created (skip API call)
      existing_session_id = request.headers.get("X-Session-ID")
      if existing_session_id:
          tracer.create_session(
              session_id=existing_session_id,
              skip_api_call=True  # Just set in baggage, no API call
          )
      else:
          tracer.create_session(session_name="new-session")
   
   **See Also:**
   
   - :meth:`acreate_session` - Async version for FastAPI and async frameworks
   - :meth:`enrich_session` - Add data to existing session
   
   .. versionadded:: 1.0.0rc8

acreate_session()
~~~~~~~~~~~~~~~~~

.. py:method:: acreate_session(session_name: Optional[str] = None, session_id: Optional[str] = None, inputs: Optional[Dict[str, Any]] = None, metadata: Optional[Dict[str, Any]] = None, user_properties: Optional[Dict[str, Any]] = None, source: Optional[str] = None) -> Optional[str]
   :async:
   :no-index:

   Async version of ``create_session()`` for async frameworks like FastAPI.
   
   Creates a session via async API call and stores session_id in baggage.
   This is the recommended method for async web servers.
   
   **Parameters:**
   
   Same as :meth:`create_session`.
   
   **Returns:**
   
   :rtype: Optional[str]
   :returns: Session ID if successful, None otherwise
   
   **FastAPI Middleware Example:**
   
   .. code-block:: python
   
      from fastapi import FastAPI, Request
      from honeyhive import HoneyHiveTracer, trace
      
      app = FastAPI()
      tracer = HoneyHiveTracer.init(api_key="...", project="my-api")
      
      @app.middleware("http")
      async def session_middleware(request: Request, call_next):
          """Create isolated session for each request."""
          session_id = await tracer.acreate_session(
              session_name=f"api-{request.url.path}",
              inputs={
                  "method": request.method,
                  "path": str(request.url),
                  "user_id": request.headers.get("X-User-ID"),
              }
          )
          
          response = await call_next(request)
          
          # enrich_session reads session_id from baggage automatically
          tracer.enrich_session(outputs={"status_code": response.status_code})
          
          if session_id:
              response.headers["X-Session-ID"] = session_id
          
          return response
      
      @app.post("/chat")
      @trace(event_type="chain", tracer=tracer)
      async def chat(message: str):
          # Span automatically uses session_id from baggage
          tracer.enrich_span(inputs={"message": message})
          return await process_message(message)
   
   **See Also:**
   
   - :meth:`create_session` - Sync version for Flask/Django
   
   .. versionadded:: 1.0.0rc8

with_session()
~~~~~~~~~~~~~~

.. py:method:: with_session(session_name: Optional[str] = None, inputs: Optional[Dict[str, Any]] = None, metadata: Optional[Dict[str, Any]] = None, user_properties: Optional[Dict[str, Any]] = None, **kwargs) -> ContextManager[Optional[str]]
   :no-index:

   Context manager that creates a session and ensures proper context cleanup.
   
   **Parameters:**
   
   :param session_name: Human-readable name for the session.
   :type session_name: Optional[str]
   
   :param inputs: Input data for the session.
   :type inputs: Optional[Dict[str, Any]]
   
   :param metadata: Additional metadata for the session.
   :type metadata: Optional[Dict[str, Any]]
   
   :param user_properties: User-specific properties.
   :type user_properties: Optional[Dict[str, Any]]
   
   **Yields:**
   
   :rtype: Optional[str]
   :yields: The newly created session ID
   
   **Usage:**
   
   .. code-block:: python
   
      # Scoped session management
      with tracer.with_session("batch-job", inputs={"batch_id": batch_id}) as session_id:
          # All spans created here use this session
          process_batch(items)
          tracer.enrich_session(outputs={"processed": len(items)})
      # Context automatically cleaned up
   
   .. versionadded:: 1.0.0rc8

enrich_session()
~~~~~~~~~~~~~~~~

.. py:method:: enrich_session(metadata: Optional[Dict[str, Any]] = None, inputs: Optional[Dict[str, Any]] = None, outputs: Optional[Dict[str, Any]] = None, config: Optional[Dict[str, Any]] = None, feedback: Optional[Dict[str, Any]] = None, metrics: Optional[Dict[str, Any]] = None, user_properties: Optional[Dict[str, Any]] = None, session_id: Optional[str] = None, **kwargs) -> None
   :no-index:

   Update the current session with additional data.
   
   Automatically reads session_id from OpenTelemetry baggage if not explicitly provided,
   making it compatible with the ``create_session()`` pattern for web servers.
   
   **Parameters:**
   
   :param inputs: Additional input data to add to session.
   :type inputs: Optional[Dict[str, Any]]
   
   :param outputs: Output/response data to add to session.
   :type outputs: Optional[Dict[str, Any]]
   
   :param metadata: Additional metadata to add to session.
   :type metadata: Optional[Dict[str, Any]]
   
   :param feedback: User feedback data.
   :type feedback: Optional[Dict[str, Any]]
   
   :param metrics: Metrics data.
   :type metrics: Optional[Dict[str, Any]]
   
   :param user_properties: User properties to add.
   :type user_properties: Optional[Dict[str, Any]]
   
   :param session_id: Explicit session ID override. If not provided, reads from baggage.
   :type session_id: Optional[str]
   
   **Usage with create_session():**
   
   .. code-block:: python
   
      @app.middleware("http")
      async def session_middleware(request: Request, call_next):
          # Create session - stores in baggage
          await tracer.acreate_session(session_name="api-request")
          
          response = await call_next(request)
          
          # enrich_session automatically reads session_id from baggage
          tracer.enrich_session(
              outputs={"status_code": response.status_code},
              metadata={"response_time_ms": response_time}
          )
          return response

Context Propagation Methods (Backwards Compatibility)
-----------------------------------------------------

link()
~~~~~~

.. py:method:: link(carrier: Optional[Dict[str, Any]] = None, getter: Optional[Any] = None) -> Any

   Link to parent context via carrier for distributed tracing (backwards compatibility).
   
   **Parameters:**
   
   :param carrier: Context propagation carrier containing trace context
   :type carrier: Optional[Dict[str, Any]]
   
   :param getter: Custom getter for extracting context from carrier
   :type getter: Optional[Any]
   
   **Returns:**
   
   :rtype: Any
   :returns: Context token for later unlinking
   
   **Usage:**
   
   .. code-block:: python
   
      # Link to parent trace from HTTP headers
      headers = {"traceparent": "00-trace-id-span-id-01"}
      token = tracer.link(headers)
      
      # Your traced operations will now be children of the parent trace
      with tracer.trace("child_operation") as span:
          span.set_attribute("linked_to_parent", True)
      
      # Unlink when done
      tracer.unlink(token)

unlink()
~~~~~~~~

.. py:method:: unlink(token: Any) -> None

   Unlink from parent context (backwards compatibility).
   
   **Parameters:**
   
   :param token: Context token returned by link() method
   :type token: Any
   
   **Usage:**
   
   .. code-block:: python
   
      # Link to parent context
      token = tracer.link(parent_carrier)
      
      try:
          # Operations linked to parent
          with tracer.trace("linked_operation"):
              do_work()
      finally:
          # Always unlink to restore original context
          tracer.unlink(token)

inject()
~~~~~~~~

.. py:method:: inject(carrier: Optional[Dict[str, Any]] = None, setter: Optional[Any] = None) -> Dict[str, Any]

   Inject current trace and baggage context into carrier (backwards compatibility).
   
   **Parameters:**
   
   :param carrier: Carrier dictionary to inject context into
   :type carrier: Optional[Dict[str, Any]]
   
   :param setter: Custom setter for injecting context into carrier
   :type setter: Optional[Any]
   
   **Returns:**
   
   :rtype: Dict[str, Any]
   :returns: Carrier with injected trace context
   
   **Usage:**
   
   .. code-block:: python
   
      # Inject current trace context into HTTP headers
      headers = {"Content-Type": "application/json"}
      headers_with_trace = tracer.inject(headers)
      
      # Make HTTP request with trace context
      response = requests.post(
          "https://api.example.com/data",
          headers=headers_with_trace,
          json=payload
      )
      
      # Or inject into empty carrier
      trace_context = tracer.inject()
      print(f"Trace context: {trace_context}")

Properties
----------

project
~~~~~~~

.. py:attribute:: project
   :type: str

   The project name associated with this tracer instance.
   
   .. code-block:: python
   
      # Uses HH_API_KEY and HH_PROJECT environment variables
      # Or specify project explicitly:
      tracer = HoneyHiveTracer.init(project="user-service")  # Or set HH_PROJECT environment variable
      print(f"Tracer project: {tracer.project}")  # "user-service"

source
~~~~~~

.. py:attribute:: source
   :type: str

   The source environment identifier for this tracer instance.
   
   .. code-block:: python
   
      # Uses HH_API_KEY and HH_PROJECT environment variables
      tracer = HoneyHiveTracer.init(
          project="your-project",  # Or set HH_PROJECT environment variable
          source="production"
      )
      print(f"Environment: {tracer.source}")  # "production"

session_id
~~~~~~~~~~

.. py:attribute:: session_id
   :type: str

   Unique session identifier for this tracer instance.
   
   .. code-block:: python
   
      # Uses HH_API_KEY and HH_PROJECT environment variables
      tracer = HoneyHiveTracer.init(
          project="your-project",  # Or set HH_PROJECT environment variable
          session_name="user-onboarding"
      )
      print(f"Session ID: {tracer.session_id}")  # Auto-generated unique ID

test_mode
~~~~~~~~~

.. py:attribute:: test_mode
   :type: bool

   Whether the tracer is in test mode (no data sent to HoneyHive).
   
   .. code-block:: python
   
      # Requires HH_API_KEY environment variable
      tracer = HoneyHiveTracer.init(
          project="your-project",          # Or set HH_PROJECT environment variable
          test_mode=True                   # Or set HH_TEST_MODE=true environment variable
      )
      if tracer.test_mode:
          print("Running in test mode - no data will be sent")

Multi-Instance Architecture
---------------------------

The HoneyHiveTracer supports multiple independent instances for flexible workflow management:

**Environment Separation:**

.. code-block:: python

   # Production tracer
   prod_tracer = HoneyHiveTracer.init(
       api_key="prod-api-key",      # Or set HH_API_KEY environment variable
       project="my-project",        # Or set HH_PROJECT environment variable
       source="production"          # Or set HH_SOURCE environment variable
   )
   
   # Staging tracer
   staging_tracer = HoneyHiveTracer.init(
       api_key="staging-api-key",   # Or set HH_API_KEY environment variable
       project="my-project",        # Or set HH_PROJECT environment variable
       source="staging"             # Or set HH_SOURCE environment variable
   )
   
   # Development tracer
   dev_tracer = HoneyHiveTracer.init(
       api_key="dev-api-key",       # Or set HH_API_KEY environment variable
       project="my-project",        # Or set HH_PROJECT environment variable
       source="development",        # Or set HH_SOURCE environment variable
       test_mode=True               # Or set HH_TEST_MODE=true environment variable
   )

**Service-Based Separation:**

.. code-block:: python

   # Microservices architecture
   # Each service uses HH_API_KEY environment variable
   auth_tracer = HoneyHiveTracer.init(
       project="auth-service",
       session_name="auth_operations"
   )
   
   user_tracer = HoneyHiveTracer.init(
       project="user-service",
       session_name="user_operations"
   )
   
   payment_tracer = HoneyHiveTracer.init(
       project="payment-service",
       session_name="payment_operations"
   )

**Workflow-Based Separation:**

.. code-block:: python

   # Different workflows with different instrumentors
   # All tracers use HH_API_KEY environment variable
   
   # Chat workflow tracer
   chat_tracer = HoneyHiveTracer.init(
       project="chat-service"
   )
   
   # Initialize instrumentor for chat workflow
   chat_instrumentor = OpenAIInstrumentor()
   chat_instrumentor.instrument(tracer_provider=chat_tracer.provider)
   
   # Analysis workflow tracer  
   analysis_tracer = HoneyHiveTracer.init(
       project="analysis-service"
   )
   
   # Initialize instrumentor for analysis workflow
   analysis_instrumentor = AnthropicInstrumentor()
   analysis_instrumentor.instrument(tracer_provider=analysis_tracer.provider)
   
   # Background tasks tracer (no LLM instrumentors needed)
   background_tracer = HoneyHiveTracer.init(
       project="background-tasks"
   )

Thread Safety
-------------

All HoneyHiveTracer instances are thread-safe and can be safely used across multiple threads:

.. code-block:: python

   import threading
   import concurrent.futures
   from honeyhive import HoneyHiveTracer, trace
   
   # Global tracer instance
   tracer = HoneyHiveTracer.init(
       api_key="your-key",  # Or set HH_API_KEY environment variable
       project="your-project"
   )
   
   @trace(tracer=tracer)
   def worker_function(worker_id: int, data: str):
       """Safe to call from multiple threads simultaneously."""
       with tracer.trace(f"worker_{worker_id}_processing") as span:
           span.set_attribute("worker.id", worker_id)
           span.set_attribute("data.length", len(data))
           
           # Simulate work
           time.sleep(random.uniform(0.1, 0.5))
           
           tracer.enrich_current_span({
               "worker.completion_time": time.time(),
               "worker.thread_id": threading.current_thread().ident
           })
           
           return f"Worker {worker_id} processed {len(data)} characters"
   
   # Concurrent execution
   with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
       futures = []
       for i in range(50):
           future = executor.submit(worker_function, i, f"data_for_worker_{i}")
           futures.append(future)
       
       # Collect results
       for future in concurrent.futures.as_completed(futures):
           result = future.result()
           print(result)

Context Propagation
-------------------

The tracer automatically handles OpenTelemetry context propagation across different execution contexts:

**Thread Context Propagation:**

.. code-block:: python

   import threading
   from opentelemetry import trace
   
   @trace(tracer=tracer, event_type="parent_operation")
   def parent_function():
       # Start a parent span
       tracer.enrich_current_span({"operation.type": "parent"})
       
       def worker():
           # Child span automatically inherits parent context
           with tracer.trace("child_operation") as span:
               span.set_attribute("operation.type", "child")
               span.set_attribute("thread.id", threading.current_thread().ident)
       
       # Start worker in separate thread
       thread = threading.Thread(target=worker)
       thread.start()
       thread.join()

**Async Context Propagation:**

.. code-block:: python

   import asyncio
   
   @trace(tracer=tracer, event_type="async_parent")
   async def async_parent():
       tracer.enrich_current_span({"operation.type": "async_parent"})
       
       # Child async operations inherit context
       await async_child()
   
   @trace(tracer=tracer, event_type="async_child")
   async def async_child():
       tracer.enrich_current_span({"operation.type": "async_child"})
       await asyncio.sleep(0.1)
   
   # Run async operations
   asyncio.run(async_parent())

**HTTP Context Propagation:**

.. code-block:: python

   import requests
   from opentelemetry.propagate import inject
   
   @trace(tracer=tracer, event_type="http_client_call")
   def make_http_request(url: str):
       headers = {"Content-Type": "application/json"}
       
       # Inject trace context into HTTP headers
       inject(headers)
       
       response = requests.get(url, headers=headers)
       
       tracer.enrich_current_span({
           "http.url": url,
           "http.status_code": response.status_code,
           "http.response_size": len(response.content)
       })
       
       return response

Error Handling and Resilience
-----------------------------

The HoneyHiveTracer is designed for production resilience with graceful degradation:

**Graceful Degradation:**

.. code-block:: python

   # If HoneyHive API is unavailable, your application continues normally
   try:
       tracer = HoneyHiveTracer.init(
           api_key="potentially_invalid_key",  # Or set HH_API_KEY environment variable
           project="your-project"              # Or set HH_PROJECT environment variable
       )
   except Exception as e:
       # Tracer initialization failed, but app can continue
       print(f"Tracing unavailable: {e}")
       tracer = None
   
   # Safe usage pattern
   def safe_trace_operation():
       if tracer:
           with tracer.trace("operation") as span:
               span.set_attribute("tracing.enabled", True)
               result = business_logic()
       else:
           # Business logic still runs without tracing
           result = business_logic()
       return result

**Automatic Exception Capture:**

.. code-block:: python

   @trace(tracer=tracer, event_type="error_prone_operation")
   def operation_that_might_fail():
       if random.random() < 0.3:
           raise ValueError("Simulated failure")
       elif random.random() < 0.6:
           raise ConnectionError("Network issue")
       return "Success!"
   
   # The tracer automatically captures:
   # - Exception type and message
   # - Stack trace
   # - Execution time up to failure
   # - Span status marking as error
   
   try:
       result = operation_that_might_fail()
   except Exception as e:
       # Exception info is already captured in the trace
       print(f"Operation failed: {e}")

**Retry Logic Integration:**

.. code-block:: python

   import time
   from functools import wraps
   
   def with_retry(max_retries=3, delay=1.0):
       def decorator(func):
           @wraps(func)
           def wrapper(*args, **kwargs):
               for attempt in range(max_retries):
                   try:
                       with tracer.trace(f"{func.__name__}_attempt_{attempt + 1}") as span:
                           span.set_attribute("retry.attempt", attempt + 1)
                           span.set_attribute("retry.max_attempts", max_retries)
                           
                           result = func(*args, **kwargs)
                           
                           span.set_attribute("retry.success", True)
                           span.set_attribute("retry.final_attempt", attempt + 1)
                           return result
                           
                   except Exception as e:
                       span.set_attribute("retry.success", False)
                       span.set_attribute("retry.error", str(e))
                       
                       if attempt == max_retries - 1:
                           span.set_attribute("retry.exhausted", True)
                           raise
                       
                       time.sleep(delay * (2 ** attempt))  # Exponential backoff
           return wrapper
       return decorator
   
   @with_retry(max_retries=3, delay=0.5)
   @trace(tracer=tracer, event_type="external_api_call")
   def call_external_api():
       # Potentially flaky external API call
       response = requests.get("https://api.example.com/data", timeout=5)
       response.raise_for_status()
       return response.json()

Framework Integration Examples
------------------------------

**Flask Integration:**

.. code-block:: python

   from flask import Flask, request, g
   
   app = Flask(__name__)
   # Requires HH_API_KEY environment variable
   tracer = HoneyHiveTracer.init(project="flask-app")
   
   @app.before_request
   def start_trace():
       g.span = tracer.trace(f"{request.method} {request.path}")
       g.span.__enter__()
       g.span.set_attribute("http.method", request.method)
       g.span.set_attribute("http.url", request.url)
       g.span.set_attribute("http.user_agent", request.headers.get("User-Agent", ""))
   
   @app.after_request
   def end_trace(response):
       if hasattr(g, 'span'):
           g.span.set_attribute("http.status_code", response.status_code)
           g.span.set_attribute("http.response_size", len(response.get_data()))
           g.span.__exit__(None, None, None)
       return response
   
   @app.route("/users/<user_id>")
   def get_user(user_id):
       with tracer.trace("get_user_operation") as span:
           span.set_attribute("user.id", user_id)
           
           # Your business logic here
           user_data = fetch_user_from_db(user_id)
           
           span.set_attribute("user.found", user_data is not None)
           return {"user": user_data}

**FastAPI Integration (Multi-Session Pattern):**

This pattern creates isolated sessions for each request, enabling safe concurrent request handling:

.. code-block:: python

   from fastapi import FastAPI, Request
   from honeyhive import HoneyHiveTracer, trace
   import os
   
   app = FastAPI()
   
   # Initialize tracer ONCE at app startup (shared across all requests)
   tracer = HoneyHiveTracer.init(
       api_key=os.getenv("HH_API_KEY"),
       project="fastapi-app",
       source="production"
   )
   
   @app.middleware("http")
   async def session_middleware(request: Request, call_next):
       """Create isolated session for each request using baggage.
       
       acreate_session() stores session_id in OpenTelemetry baggage,
       which is ContextVar-based and therefore request-scoped.
       This enables safe concurrent request handling.
       """
       session_id = await tracer.acreate_session(
           session_name=f"api-{request.url.path}",
           inputs={
               "method": request.method,
               "path": str(request.url),
               "user_id": request.headers.get("X-User-ID"),
           }
       )
       
       response = await call_next(request)
       
       # enrich_session reads session_id from baggage automatically
       tracer.enrich_session(
           outputs={"status_code": response.status_code}
       )
       
       # Optionally return session_id to client
       if session_id:
           response.headers["X-Session-ID"] = session_id
       
       return response
   
   @app.post("/chat")
   @trace(event_type="chain", tracer=tracer)
   async def chat_endpoint(message: str):
       """Endpoint with automatic session association.
       
       The span processor reads session_id from baggage (set by middleware).
       """
       tracer.enrich_span(metadata={"message_length": len(message)})
       result = await process_message(message)
       return {"response": result}
   
   @trace(event_type="tool", tracer=tracer)
   async def process_message(message: str) -> str:
       """Nested spans automatically use request's session context."""
       # Simulate LLM call
       response = f"Response to: {message}"
       tracer.enrich_span(
           inputs={"message": message},
           outputs={"response": response}
       )
       return response

.. note::
   **Why acreate_session() instead of session_start()?**
   
   ``acreate_session()`` stores session_id in OpenTelemetry baggage (ContextVar-based),
   which provides request-scoped isolation. ``session_start()`` stores on the tracer
   instance, which causes race conditions in concurrent environments.

**Bring-Your-Own Session ID:**

If you already have a session ID (e.g., from a database or external system):

.. code-block:: python

   @app.middleware("http")
   async def session_middleware(request: Request, call_next):
       # Use existing session ID from headers or generate new one
       existing_session_id = request.headers.get("X-Session-ID")
       
       if existing_session_id:
           # Just set it in baggage - no API call needed
           await tracer.acreate_session(session_id=existing_session_id)
       else:
           # Create new session via API
           await tracer.acreate_session(
               session_name=f"api-{request.url.path}",
               inputs={"method": request.method}
           )
       
       return await call_next(request)

**Django Integration:**

.. code-block:: python

   # middleware.py
   from django.utils.deprecation import MiddlewareMixin
   from honeyhive import HoneyHiveTracer
   
   # Requires HH_API_KEY environment variable
   tracer = HoneyHiveTracer.init(project="django-app")
   
   class HoneyHiveMiddleware(MiddlewareMixin):
       def process_request(self, request):
           request.honeyhive_span = tracer.trace(f"{request.method} {request.path}")
           request.honeyhive_span.__enter__()
           
           request.honeyhive_span.set_attribute("http.method", request.method)
           request.honeyhive_span.set_attribute("http.path", request.path)
           request.honeyhive_span.set_attribute("http.user_agent", 
                                               request.META.get("HTTP_USER_AGENT", ""))
       
       def process_response(self, request, response):
           if hasattr(request, 'honeyhive_span'):
               request.honeyhive_span.set_attribute("http.status_code", response.status_code)
               request.honeyhive_span.__exit__(None, None, None)
           return response
   
   # views.py
   from django.http import JsonResponse
   from django.conf import settings
   
   def user_detail(request, user_id):
       with settings.HONEYHIVE_TRACER.trace("get_user_detail") as span:
           span.set_attribute("user.id", user_id)
           
           # Your Django logic here
           user_data = {"id": user_id, "name": "User Name"}
           
           span.set_attribute("user.found", True)
           return JsonResponse(user_data)

Performance Considerations
--------------------------

**Batching and Sampling:**

.. code-block:: python

   # For high-throughput applications, consider sampling
   import random
   
   def should_trace():
       return random.random() < 0.1  # 10% sampling
   
   @trace(tracer=tracer if should_trace() else None)
   def high_volume_operation():
       # Only 10% of calls will be traced
       pass

**Efficient Attribute Setting:**

.. code-block:: python

   # Batch attribute setting for better performance
   with tracer.trace("efficient_operation") as span:
       # Instead of multiple set_attribute calls
       attributes = {
           "user.id": user_id,
           "user.tier": user_tier,
           "operation.type": "batch",
           "operation.size": batch_size,
           "operation.priority": priority
       }
       
       # Set all at once
       for key, value in attributes.items():
           span.set_attribute(key, value)

Best Practices
--------------

**Naming Conventions:**

.. code-block:: python

   # Good: Descriptive, hierarchical names
   with tracer.trace("user.authentication.login"):
       pass
   
   with tracer.trace("payment.processing.stripe.charge"):
       pass
   
   with tracer.trace("llm.openai.completion.gpt4"):
       pass
   
   # Avoid: Generic or unclear names
   with tracer.trace("operation"):  # Too generic
       pass
   
   with tracer.trace("func1"):  # Not descriptive
       pass

**Consistent Attribute Patterns:**

.. code-block:: python

   # Establish consistent attribute patterns across your application
   with tracer.trace("user_operation") as span:
       # User-related attributes
       span.set_attribute("user.id", user_id)
       span.set_attribute("user.email", user_email)
       span.set_attribute("user.tier", user_tier)
       
       # Operation-related attributes  
       span.set_attribute("operation.type", "user_update")
       span.set_attribute("operation.duration", duration)
       span.set_attribute("operation.success", success)
       
       # Resource-related attributes
       span.set_attribute("resource.database", "users")
       span.set_attribute("resource.table", "user_profiles")

**Resource Management:**

.. code-block:: python

   # Ensure proper cleanup in long-running applications
   import atexit
   import signal
   import sys
   
   tracer = HoneyHiveTracer.init(project="your-project")  # Requires HH_API_KEY environment variable
   
   def cleanup_handler(signum=None, frame=None):
       print("Shutting down, flushing traces...")
       tracer.flush(timeout=10.0)
       tracer.close()
       if signum:
           sys.exit(0)
   
   # Register cleanup handlers
   atexit.register(cleanup_handler)
   signal.signal(signal.SIGINT, cleanup_handler)
   signal.signal(signal.SIGTERM, cleanup_handler)

See Also
--------

- :doc:`decorators` - ``@trace`` and ``@trace_class`` decorator reference
- :doc:`client` - HoneyHive client API reference
