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

# OpenTelemetry Integration

> Automatically generate service-delivery graphs from your existing OpenTelemetry traces.

Threadify is a service-delivery intelligence system. The Threadify SDK supports converting standard OpenTelemetry (OTel) traces into Threadify's service-delivery concepts, thereby providing deep, actionable intelligence into exactly how you are delivering to your customers.

If your application is already instrumented with OpenTelemetry (OTel), you do **not** need to manually add `thread.step()` calls throughout your codebase. Instead, you can use the Threadify OpenTelemetry Exporter to seamlessly translate your existing traces into Threadify execution graphs.

## The Mapping Model

The Threadify Exporter automatically translates the highly technical OTel hierarchy into business-friendly language:

| OpenTelemetry Concept | Threadify Concept | Description                                                                      |
| :-------------------- | :---------------- | :------------------------------------------------------------------------------- |
| **Trace**             | **Thread**        | A Trace represents an entire business workflow (e.g., *Customer Checkout*).      |
| **Span**              | **Step**          | A Span represents a major operation (e.g., *process\_payment*).                  |
| **Span Event**        | **Sub-Step**      | An Event is a specific milestone during the operation (e.g., *validated\_card*). |
| **Span Link**         | **Link**          | *Coming Soon.*                                                                   |

## Status Mapping

The Exporter automatically translates the OpenTelemetry `SpanStatus` into Threadify step outcomes. This ensures that technical failures (and even ambiguous states) are correctly captured as service-delivery intelligence.

| Span Status        | Threadify Outcome | Rationale                                                                 |
| :----------------- | :---------------- | :------------------------------------------------------------------------ |
| `SpanStatus.OK`    | **success**       | Explicitly marked as successful by the developer.                         |
| `SpanStatus.ERROR` | **failed**        | Explicitly marked as a failure or exception.                              |
| `SpanStatus.UNSET` | **success**       | The default status. Threadify treats "no error" as a successful delivery. |

<Note>
  By following the standard OpenTelemetry convention where `UNSET` implies success, Threadify ensures zero-friction integration for existing instrumented applications.
</Note>

## 1. Setup the Exporter

Integrating the exporter into your application takes just a few lines of code. You initialize the Exporter and register it with your global OpenTelemetry `TracerProvider`.

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    import { Threadify } from '@threadify/sdk';
    import { trace } from '@opentelemetry/api';
    import { BasicTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';

    // 1. Establish your Threadify Connection
    const connection = await Threadify.connect('your-api-key', 'your-service');

    // 2. Create the Threadify Exporter
    // Map span attributes to Threadify "Refs" using custom ref keys
    const exporter = connection.createSpanExporter({
      refs: { orderId: 'order.id', customerId: 'customer.id' }
    });

    // Optionally filter out noisy spans by name (exact match or prefix wildcard with *)
    const exporter = connection.createSpanExporter({
      refs: { orderId: 'order.id', customerId: 'customer.id' },
      filters: ['invoke_llm', 'adk.before*', 'llm.*']
    });

    // 3. Attach the Exporter to OpenTelemetry
    const provider = new BasicTracerProvider({
      spanProcessors: [new SimpleSpanProcessor(exporter)]
    });
    trace.setGlobalTracerProvider(provider);
    ```
  </Tab>

  <Tab title="Go">
    Because Go is statically typed, the OpenTelemetry integration is provided via a separate sub-module so the core Threadify SDK remains lightweight.

    ```bash theme={null}
    go get github.com/ThreadifyDev/go-sdk/otel
    ```

    ```go theme={null}
    import (
        "context"

        "go.opentelemetry.io/otel"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"

        threadify "github.com/ThreadifyDev/go-sdk"
        threadifyotel "github.com/ThreadifyDev/go-sdk/otel"
    )

    func main() {
        ctx := context.Background()

        // 1. Establish your Threadify Connection
        conn, _ := threadify.Connect(ctx, "your-api-key",
            threadify.WithServiceName("my-service"),
        )
        defer conn.Close()

        // 2. Create the Threadify Exporter
        exporter := threadifyotel.NewSpanExporter(conn, threadifyotel.SpanExporterOptions{
            Refs: map[string]string{"orderId": "order.id", "customerId": "customer.id"},
        })

        // Optionally filter out noisy spans by name (exact match or prefix wildcard with *)
        exporter = threadifyotel.NewSpanExporter(conn, threadifyotel.SpanExporterOptions{
            Refs:    map[string]string{"orderId": "order.id", "customerId": "customer.id"},
            Filters: []string{"invoke_llm", "adk.before*", "llm.*"},
        })

        // 3. Attach the Exporter to OpenTelemetry
        provider := sdktrace.NewTracerProvider(
            sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(provider)
        defer provider.Shutdown(ctx)

        // ... your application code
    }
    ```
  </Tab>

  <Tab title="Python">
    The Python SDK includes the OpenTelemetry SpanExporter in the core package.

    ```bash theme={null}
    pip install opentelemetry-api opentelemetry-sdk
    ```

    ```python theme={null}
    from opentelemetry import trace
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from threadify import Threadify

    async def main():
        # 1. Establish your Threadify Connection
        conn = await Threadify.connect(
            "your-api-key",
            service_name="my-service",
        )

        # 2. Create the Threadify Exporter
        exporter = conn.create_span_exporter(
            options={"refs": {"orderId": "order.id", "customerId": "customer.id"}}
        )

        # Optionally filter out noisy spans by name (exact match or prefix wildcard with *)
        exporter = conn.create_span_exporter(
            options={
                "refs": {"orderId": "order.id", "customerId": "customer.id"},
                "filters": ["invoke_llm", "adk.before*", "llm.*"],
            }
        )

        # 3. Attach the Exporter to OpenTelemetry
        provider = TracerProvider()
        provider.add_span_processor(BatchSpanProcessor(exporter))
        trace.set_tracer_provider(provider)

        # ... your application code
    ```
  </Tab>
</Tabs>

<Note>
  By default, the Exporter will automatically push Spans to Threadify whenever they end. It manages the underlying Threadify Thread lifecycles for you transparently.
</Note>

## 2. Filtering Spans

Some applications generate a high volume of internal or utility spans that you may not want to send to Threadify. Use the `filters` (Node.js/Go) or `"filters"` (Python) option to drop spans by name before they are exported.

| Filter Pattern  | Behavior              | Example Match                        |
| :-------------- | :-------------------- | :----------------------------------- |
| `"invoke_llm"`  | Exact match           | `invoke_llm`                         |
| `"adk.before*"` | Prefix wildcard (`*`) | `adk.before_tool_call`, `adk.before` |
| `"llm.*"`       | Prefix wildcard (`*`) | `llm.chat_completion`, `llm.`        |

<Note>
  Filters are applied on the span name only. Trailing `*` acts as a prefix wildcard. Empty filters are skipped.
</Note>

## 3. Using OpenTelemetry Natively

Once configured, you simply use the standard OpenTelemetry API. You do not need to import or call Threadify directly in your business logic!

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    // Get your standard OTel tracer
    const tracer = trace.getTracer('my-service-tracer');

    // Start a span (This automatically ensures a Thread exists in Threadify)
    const span = tracer.startSpan('process_payment');

    // Set explicit Threadify mapping overrides (Optional)
    // This tells Threadify to validate this thread against the 'payment_workflow' contract
    span.setAttribute('threadify.contract', 'payment_workflow');

    // Tag the thread for filtering and categorization
    span.setAttribute('threadify.tags', ['production', 'v2.1']);

    // Set attributes
    span.setAttribute('order.id', 'ORD-98765'); // Extracted as a Ref (based on setup)
    span.setAttribute('payment.amount', 42.50); // Extracted as standard Context

    // Add Span Events (These automatically become Threadify Sub-Steps!)
    span.addEvent('validated_credit_card', {
      'card.type': 'visa',
      'fraud.score': 0.05
    });

    // Simulate work...
    await processPayment();

    // End the span (This triggers the Exporter and pushes the Step to Threadify)
    span.end();
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"

        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        "go.opentelemetry.io/otel/trace"
    )

    // Get your standard OTel tracer
    tracer := otel.Tracer("my-service-tracer")

    // Start a span (This automatically ensures a Thread exists in Threadify)
    ctx, span := tracer.Start(context.Background(), "process_payment")

    // Set explicit Threadify mapping overrides (Optional)
    // This tells Threadify to validate this thread against the 'payment_workflow' contract
    span.SetAttributes(attribute.String("threadify.contract", "payment_workflow"))

    // Tag the thread for filtering and categorization
    span.SetAttributes(attribute.StringSlice("threadify.tags", []string{"production", "v2.1"}))

    // Set attributes
    span.SetAttributes(attribute.String("order.id", "ORD-98765"))       // Extracted as a Ref (based on setup)
    span.SetAttributes(attribute.Float64("payment.amount", 42.50))     // Extracted as standard Context

    // Add Span Events (These automatically become Threadify Sub-Steps!)
    span.AddEvent("validated_credit_card", trace.WithAttributes(
        attribute.String("card.type", "visa"),
        attribute.Float64("fraud.score", 0.05),
    ))

    // Simulate work...
    processPayment(ctx)

    // End the span (This triggers the Exporter and pushes the Step to Threadify)
    span.End()
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from opentelemetry import trace

    # Get your standard OTel tracer
    tracer = trace.get_tracer("my-service-tracer")

    # Start a span (This automatically ensures a Thread exists in Threadify)
    with tracer.start_as_current_span("process_payment") as span:
        # Set explicit Threadify mapping overrides (Optional)
        # This tells Threadify to validate this thread against the 'payment_workflow' contract
        span.set_attribute("threadify.contract", "payment_workflow")

        # Tag the thread for filtering and categorization
        span.set_attribute("threadify.tags", ["production", "v2.1"])

        # Set attributes
        span.set_attribute("order.id", "ORD-98765")       # Extracted as a Ref (based on setup)
        span.set_attribute("payment.amount", 42.50)     # Extracted as standard Context

        # Add Span Events (These automatically become Threadify Sub-Steps!)
        span.add_event("validated_credit_card", {
            "card.type": "visa",
            "fraud.score": 0.05,
        })

        # Simulate work...
        process_payment()

        # Span ends automatically (This triggers the Exporter)
    ```
  </Tab>
</Tabs>

## 4. Advanced Attributes (Optional)

If you want finer control over how a specific Span maps to Threadify, you can set the following special `threadify.*` attributes on your Span. If omitted, the Exporter will use sensible defaults.

| Attribute             | Description                                                                            | Default Fallback                      |
| :-------------------- | :------------------------------------------------------------------------------------- | :------------------------------------ |
| `threadify.contract`  | The Contract Name to validate this workflow against.                                   | `null` (Schema-less Thread)           |
| `threadify.step_name` | Override the Step name in Threadify.                                                   | Uses `span.name`                      |
| `threadify.thread_id` | Force the Span to attach to a specific existing Thread ID.                             | Auto-generates a new Thread per Trace |
| `threadify.label`     | The human-readable label for the Thread.                                               | Uses `span.name`                      |
| `threadify.service`   | Override the service name that executed this step.                                     | Uses the connection's default service |
| `threadify.tags`      | Immutable labels attached to the thread at creation time (e.g., `production`, `v2.1`). | `null`                                |

Any attributes starting with `threadify.ref.*` or `threadify.context.*` will be explicitly mapped to references or context respectively, bypassing the default configuration rules.
