diff --git a/tutorials/tracing/span_filtering_tutorial.ipynb b/tutorials/tracing/span_filtering_tutorial.ipynb
new file mode 100644
index 0000000000..e16250ac94
--- /dev/null
+++ b/tutorials/tracing/span_filtering_tutorial.ipynb
@@ -0,0 +1,283 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ " Docs\n",
+ " |\n",
+ " GitHub\n",
+ " |\n",
+ " Community\n",
+ "
\n",
+ "\n",
+ "Filter OpenTelemetry Spans
\n",
+ "\n",
+ "This tutorial shows how to filter OpenTelemetry spans based on a condition.\n",
+ "\n",
+ "If you're using multiple OTEL-compatible libraries, you may want to filter spans based on a condition. This can prevent you from sending spans to multiple backends unnecessarily.\n",
+ "\n",
+ "There are multiple approaches to do this, in this tutorial we'll show how to do this using a custom SpanProcessor."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Install Dependencies"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install -q uv\n",
+ "!uv pip install --system -q opentelemetry-sdk opentelemetry-exporter-otlp-proto-http openinference-instrumentation-openai openai python-dotenv"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "from dotenv import load_dotenv\n",
+ "from opentelemetry import trace as trace_api\n",
+ "from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter\n",
+ "from opentelemetry.sdk.trace import TracerProvider\n",
+ "from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SpanExporter, SpanProcessor\n",
+ "\n",
+ "load_dotenv()\n",
+ "\n",
+ "from getpass import getpass\n",
+ "\n",
+ "import openai\n",
+ "from openinference.instrumentation.openai import OpenAIInstrumentor"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Define a custom SpanProcessor\n",
+ "\n",
+ "We'll extend the SpanProcessor class to add a condition that determines whether a span should be exported."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class ConditionalSpanProcessor(SpanProcessor):\n",
+ " def __init__(self, exporter: SpanExporter, condition: callable):\n",
+ " self.exporter = exporter\n",
+ " self.condition = condition\n",
+ "\n",
+ " def on_start(self, span, parent_context):\n",
+ " pass\n",
+ "\n",
+ " def on_end(self, span):\n",
+ " # Only export spans that meet the condition\n",
+ " if self.condition(span):\n",
+ " self.exporter.export([span])\n",
+ "\n",
+ " def shutdown(self):\n",
+ " self.exporter.shutdown()\n",
+ "\n",
+ " def force_flush(self, timeout_millis=None):\n",
+ " self.exporter.force_flush(timeout_millis)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Along with this class, we'll define two conditions: one for sending spans to the console, and one for sending spans to Phoenix."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define conditions for sending spans to specific exporters\n",
+ "def console_condition(span):\n",
+ " return \"console\" in span.name # Example: send to Console if \"console\" is in the span name\n",
+ "\n",
+ "\n",
+ "def phoenix_condition(span):\n",
+ " # return \"phoenix\" in span.name # Example: send to Phoenix if \"phoenix\" is in the span name\n",
+ " return not console_condition(\n",
+ " span\n",
+ " ) # Example: send to Phoenix if \"console\" is not in the span name"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Use our custom SpanProcessor to set up instrumentation\n",
+ "In this example, we'll use Phoenix as one of our destinations, and the console as the other. You could instead add any other exporters you'd like in this approach.\n",
+ "\n",
+ "If you need to set up an API key for Phoenix, you can do so [here](https://app.phoenix.arize.com/)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "if not (phoenix_api_key := os.getenv(\"PHOENIX_API_KEY\")):\n",
+ " phoenix_api_key = getpass(\"Enter your Phoenix API Key: \")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Define the console exporter"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tracer_provider = TracerProvider()\n",
+ "\n",
+ "# Create the Console exporter\n",
+ "console_exporter = ConsoleSpanExporter()\n",
+ "\n",
+ "# Add the Console exporter to the tracer provider\n",
+ "tracer_provider.add_span_processor(ConditionalSpanProcessor(console_exporter, console_condition))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Define the Phoenix exporter"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Add Phoenix API Key to the headers for tracing and API access\n",
+ "os.environ[\"PHOENIX_CLIENT_HEADERS\"] = f\"api_key={phoenix_api_key}\"\n",
+ "\n",
+ "# Create the Phoenix exporter\n",
+ "otlp_exporter = OTLPSpanExporter(endpoint=\"https://app.phoenix.arize.com/v1/traces\")\n",
+ "\n",
+ "# Add the Phoenix exporter to the tracer provider\n",
+ "tracer_provider.add_span_processor(ConditionalSpanProcessor(otlp_exporter, phoenix_condition))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Add the exporters to the tracer provider"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set the tracer provider\n",
+ "trace_api.set_tracer_provider(tracer_provider)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Run our app and view traces"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "if not (openai_api_key := os.getenv(\"OPENAI_API_KEY\")):\n",
+ " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter your OpenAI API Key: \")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This first span will be exported to the console. Here we're using manual instrumentation to create the span."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Create a tracer\n",
+ "tracer = trace_api.get_tracer(__name__)\n",
+ "\n",
+ "# Example of creating and exporting spans\n",
+ "with tracer.start_as_current_span(\"console-span\"):\n",
+ " print(\"This span will be exported to Console only.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This next span will be exported only to Phoenix. In this case, we're using our auto-instrumentor for OpenAI to generate the span."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Auto-instrumentors can still be used with this setup\n",
+ "OpenAIInstrumentor().instrument(tracer_provider=tracer_provider, skip_dep_check=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "client = openai.OpenAI()\n",
+ "client.chat.completions.create(\n",
+ " model=\"gpt-4o-mini\", messages=[{\"role\": \"user\", \"content\": \"Hello, world!\"}]\n",
+ ")"
+ ]
+ }
+ ],
+ "metadata": {
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}