From ac9e23110b5d8c5c8ef0eb83d5f71722853dcb2d Mon Sep 17 00:00:00 2001 From: Zhikuan Wei Date: Sun, 12 Apr 2026 23:52:52 -0400 Subject: [PATCH 1/6] add new example for multi-destination exporting --- .../multi-destination-exporting/README.rst | 63 ++++++++++++++++++ .../multi_destination_logs.py | 66 +++++++++++++++++++ .../multi_destination_metrics.py | 55 ++++++++++++++++ .../multi_destination_traces.py | 58 ++++++++++++++++ 4 files changed, 242 insertions(+) create mode 100644 docs/examples/multi-destination-exporting/README.rst create mode 100644 docs/examples/multi-destination-exporting/multi_destination_logs.py create mode 100644 docs/examples/multi-destination-exporting/multi_destination_metrics.py create mode 100644 docs/examples/multi-destination-exporting/multi_destination_traces.py diff --git a/docs/examples/multi-destination-exporting/README.rst b/docs/examples/multi-destination-exporting/README.rst new file mode 100644 index 0000000000..93678bc369 --- /dev/null +++ b/docs/examples/multi-destination-exporting/README.rst @@ -0,0 +1,63 @@ +Multi-Destination Exporting +=========================== + +This example shows how to export telemetry data to multiple destinations +simultaneously. As described in the `OTLP specification +`_, +each destination should have implemented its own queuing, acknowledgement +handling, and retry logic to prevent one slow or unavailable destination +from blocking the others. + +The OpenTelemetry Python SDK achieves this by using a separate processor +or reader per destination: + +* **Traces**: Use one ``BatchSpanProcessor`` per destination, each wrapping + its own ``SpanExporter``. Add each processor to the ``TracerProvider`` + via ``add_span_processor()``. + +* **Metrics**: Pass multiple ``MetricReader`` instances (each wrapping its + own ``MetricExporter``) to the ``MeterProvider`` constructor via the + ``metric_readers`` parameter. + +* **Logs**: Use one ``BatchLogRecordProcessor`` per destination, each + wrapping its own ``LogExporter``. Add each processor to the + ``LoggerProvider`` via ``add_log_record_processor()``. + +.. note:: + + The **Profiles** signal is not yet supported in the Python SDK. + When it becomes available, the same pattern will apply. + +The source files of these examples are available :scm_web:`here `. + +Installation +------------ + +.. code-block:: sh + + pip install opentelemetry-api + pip install opentelemetry-sdk + pip install opentelemetry-exporter-otlp-proto-grpc + pip install opentelemetry-exporter-otlp-proto-http + +Run the Example +--------------- + +.. code-block:: sh + + python multi_destination_traces.py + python multi_destination_metrics.py + python multi_destination_logs.py + +The output will be shown in the console for the ``ConsoleSpanExporter`` +and ``ConsoleMetricExporter`` destinations. The OTLP destinations require +a running collector. + +Useful links +------------ + +- `OTLP multi-destination exporting spec `_ +- OpenTelemetry_ +- :doc:`../../api/trace` + +.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ diff --git a/docs/examples/multi-destination-exporting/multi_destination_logs.py b/docs/examples/multi-destination-exporting/multi_destination_logs.py new file mode 100644 index 0000000000..b37550fc30 --- /dev/null +++ b/docs/examples/multi-destination-exporting/multi_destination_logs.py @@ -0,0 +1,66 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This example shows how to export logs to multiple destinations. +Each BatchLogRecordProcessor has its own queue and retry logic, so +destinations do not block each other. +""" + +import logging + +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter as GrpcLogExporter, +) +from opentelemetry.exporter.otlp.proto.http._log_exporter import ( + OTLPLogExporter as HttpLogExporter, +) +from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry.sdk._logs.export import ( + BatchLogRecordProcessor, + ConsoleLogRecordExporter, +) + +logger_provider = LoggerProvider() +set_logger_provider(logger_provider) + +# Destination 1: OTLP over gRPC +grpc_exporter = GrpcLogExporter( + endpoint="http://localhost:4317", insecure=True +) +logger_provider.add_log_record_processor( + BatchLogRecordProcessor(grpc_exporter) +) + +# Destination 2: OTLP over HTTP +http_exporter = HttpLogExporter( + endpoint="http://localhost:4318/v1/logs" +) +logger_provider.add_log_record_processor( + BatchLogRecordProcessor(http_exporter) +) + +# Destination 3: Console (for debugging) +logger_provider.add_log_record_processor( + BatchLogRecordProcessor(ConsoleLogRecordExporter()) +) + +# Use Python's standard logging, bridged to OpenTelemetry +logger = logging.getLogger("myapp") +logger.setLevel(logging.INFO) +logger.info("Logs are exported to all three destinations.") +logger.warning("This warning also goes everywhere.") + +logger_provider.shutdown() diff --git a/docs/examples/multi-destination-exporting/multi_destination_metrics.py b/docs/examples/multi-destination-exporting/multi_destination_metrics.py new file mode 100644 index 0000000000..db43d4dce5 --- /dev/null +++ b/docs/examples/multi-destination-exporting/multi_destination_metrics.py @@ -0,0 +1,55 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This example shows how to export metrics to multiple destinations. +Each PeriodicExportingMetricReader has its own collection interval +and export queue, so destinations do not block each other. +""" + +from opentelemetry import metrics +from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( + OTLPMetricExporter as GrpcMetricExporter, +) +from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( + OTLPMetricExporter as HttpMetricExporter, +) +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics.export import ( + ConsoleMetricExporter, + PeriodicExportingMetricReader, +) + +# Each reader has its own export interval and exporter +grpc_reader = PeriodicExportingMetricReader( + GrpcMetricExporter(endpoint="http://localhost:4317", insecure=True) +) +http_reader = PeriodicExportingMetricReader( + HttpMetricExporter(endpoint="http://localhost:4318/v1/metrics") +) +console_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) + +# Pass all readers to the MeterProvider +provider = MeterProvider(metric_readers=[grpc_reader, http_reader, console_reader]) +metrics.set_meter_provider(provider) + +meter = metrics.get_meter(__name__) +counter = meter.create_counter("request.count", description="Number of requests") + +counter.add(1, {"endpoint": "/api/users"}) +counter.add(1, {"endpoint": "/api/orders"}) + +print("Metrics are exported to all three destinations.") + +provider.shutdown() diff --git a/docs/examples/multi-destination-exporting/multi_destination_traces.py b/docs/examples/multi-destination-exporting/multi_destination_traces.py new file mode 100644 index 0000000000..d1bed77b76 --- /dev/null +++ b/docs/examples/multi-destination-exporting/multi_destination_traces.py @@ -0,0 +1,58 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This example shows how to export traces to multiple destinations. +Each BatchSpanProcessor has its own queue and retry logic, so +destinations do not block each other. +""" + +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( + OTLPSpanExporter as GrpcSpanExporter, +) +from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( + OTLPSpanExporter as HttpSpanExporter, +) +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import ( + BatchSpanProcessor, + ConsoleSpanExporter, +) + +provider = TracerProvider() +trace.set_tracer_provider(provider) + +# Destination 1: OTLP over gRPC +grpc_exporter = GrpcSpanExporter( + endpoint="http://localhost:4317", insecure=True +) +provider.add_span_processor(BatchSpanProcessor(grpc_exporter)) + +# Destination 2: OTLP over HTTP +http_exporter = HttpSpanExporter( + endpoint="http://localhost:4318/v1/traces" +) +provider.add_span_processor(BatchSpanProcessor(http_exporter)) + +# Destination 3: Console (for debugging) +provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter())) + +tracer = trace.get_tracer(__name__) + +with tracer.start_as_current_span("example-request"): + with tracer.start_as_current_span("fetch-data"): + print("Spans are exported to all three destinations.") + +provider.shutdown() From f3eb860532f500c1bde3a31ca9a310b9857f0142 Mon Sep 17 00:00:00 2001 From: Zhikuan Wei Date: Mon, 13 Apr 2026 23:14:35 -0400 Subject: [PATCH 2/6] update comments --- .../multi-destination-exporting/multi_destination_logs.py | 8 ++++++-- .../multi_destination_metrics.py | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/examples/multi-destination-exporting/multi_destination_logs.py b/docs/examples/multi-destination-exporting/multi_destination_logs.py index b37550fc30..43d52945aa 100644 --- a/docs/examples/multi-destination-exporting/multi_destination_logs.py +++ b/docs/examples/multi-destination-exporting/multi_destination_logs.py @@ -27,6 +27,7 @@ from opentelemetry.exporter.otlp.proto.http._log_exporter import ( OTLPLogExporter as HttpLogExporter, ) +from opentelemetry.instrumentation.logging.handler import LoggingHandler from opentelemetry.sdk._logs import LoggerProvider from opentelemetry.sdk._logs.export import ( BatchLogRecordProcessor, @@ -57,9 +58,12 @@ BatchLogRecordProcessor(ConsoleLogRecordExporter()) ) -# Use Python's standard logging, bridged to OpenTelemetry +# Bridge Python's logging to OpenTelemetry +handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider) +logging.getLogger().setLevel(logging.NOTSET) +logging.getLogger().addHandler(handler) + logger = logging.getLogger("myapp") -logger.setLevel(logging.INFO) logger.info("Logs are exported to all three destinations.") logger.warning("This warning also goes everywhere.") diff --git a/docs/examples/multi-destination-exporting/multi_destination_metrics.py b/docs/examples/multi-destination-exporting/multi_destination_metrics.py index db43d4dce5..264bf500e6 100644 --- a/docs/examples/multi-destination-exporting/multi_destination_metrics.py +++ b/docs/examples/multi-destination-exporting/multi_destination_metrics.py @@ -31,13 +31,17 @@ PeriodicExportingMetricReader, ) -# Each reader has its own export interval and exporter +# Destination 1: OTLP over gRPC grpc_reader = PeriodicExportingMetricReader( GrpcMetricExporter(endpoint="http://localhost:4317", insecure=True) ) + +# Destination 2: OTLP over HTTP http_reader = PeriodicExportingMetricReader( HttpMetricExporter(endpoint="http://localhost:4318/v1/metrics") ) + +# Destination 3: Console (for debugging) console_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) # Pass all readers to the MeterProvider From 6523e2a9794e37b39a1595e5cda3377ea0d59fdc Mon Sep 17 00:00:00 2001 From: Zhikuan Wei Date: Mon, 13 Apr 2026 23:28:02 -0400 Subject: [PATCH 3/6] better comments --- docs/examples/multi-destination-exporting/README.rst | 3 ++- .../multi-destination-exporting/multi_destination_logs.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/examples/multi-destination-exporting/README.rst b/docs/examples/multi-destination-exporting/README.rst index 93678bc369..98b5313684 100644 --- a/docs/examples/multi-destination-exporting/README.rst +++ b/docs/examples/multi-destination-exporting/README.rst @@ -39,6 +39,7 @@ Installation pip install opentelemetry-sdk pip install opentelemetry-exporter-otlp-proto-grpc pip install opentelemetry-exporter-otlp-proto-http + pip install opentelemetry-instrumentation-logging # For LoggingHandler Run the Example --------------- @@ -56,7 +57,7 @@ a running collector. Useful links ------------ -- `OTLP multi-destination exporting spec `_ +- `OTLP multi-destination exporting specification `_ - OpenTelemetry_ - :doc:`../../api/trace` diff --git a/docs/examples/multi-destination-exporting/multi_destination_logs.py b/docs/examples/multi-destination-exporting/multi_destination_logs.py index 43d52945aa..455f491d59 100644 --- a/docs/examples/multi-destination-exporting/multi_destination_logs.py +++ b/docs/examples/multi-destination-exporting/multi_destination_logs.py @@ -27,6 +27,8 @@ from opentelemetry.exporter.otlp.proto.http._log_exporter import ( OTLPLogExporter as HttpLogExporter, ) + +# this is available in the opentelemetry-instrumentation-logging package from opentelemetry.instrumentation.logging.handler import LoggingHandler from opentelemetry.sdk._logs import LoggerProvider from opentelemetry.sdk._logs.export import ( From 32e5a6455b224174398333e990ef094f71771e2a Mon Sep 17 00:00:00 2001 From: Zhikuan Wei Date: Mon, 13 Apr 2026 23:31:15 -0400 Subject: [PATCH 4/6] update ending & links --- docs/examples/multi-destination-exporting/README.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/examples/multi-destination-exporting/README.rst b/docs/examples/multi-destination-exporting/README.rst index 98b5313684..5a8b2e4b95 100644 --- a/docs/examples/multi-destination-exporting/README.rst +++ b/docs/examples/multi-destination-exporting/README.rst @@ -50,9 +50,9 @@ Run the Example python multi_destination_metrics.py python multi_destination_logs.py -The output will be shown in the console for the ``ConsoleSpanExporter`` -and ``ConsoleMetricExporter`` destinations. The OTLP destinations require -a running collector. +The output will be shown in the console for the ``ConsoleSpanExporter``, +``ConsoleMetricExporter``, and ``ConsoleLogRecordExporter`` destinations. +The OTLP destinations require a running collector. Useful links ------------ @@ -60,5 +60,7 @@ Useful links - `OTLP multi-destination exporting specification `_ - OpenTelemetry_ - :doc:`../../api/trace` +- :doc:`../../api/metrics` +- :doc:`../../api/_logs` .. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ From 0ebdfaf7feb89e1f26dbd8d25578a3364fd5d7c4 Mon Sep 17 00:00:00 2001 From: Zhikuan Wei Date: Tue, 14 Apr 2026 21:57:12 -0400 Subject: [PATCH 5/6] add link to the collector spec --- docs/examples/multi-destination-exporting/README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/examples/multi-destination-exporting/README.rst b/docs/examples/multi-destination-exporting/README.rst index 5a8b2e4b95..e5b0720f92 100644 --- a/docs/examples/multi-destination-exporting/README.rst +++ b/docs/examples/multi-destination-exporting/README.rst @@ -52,7 +52,8 @@ Run the Example The output will be shown in the console for the ``ConsoleSpanExporter``, ``ConsoleMetricExporter``, and ``ConsoleLogRecordExporter`` destinations. -The OTLP destinations require a running collector. +The OTLP destinations require a running `collector +`_. Useful links ------------ From d06c744e5064cd77f35261b8a579ff879bdf9549 Mon Sep 17 00:00:00 2001 From: Zhikuan Wei Date: Tue, 14 Apr 2026 22:35:37 -0400 Subject: [PATCH 6/6] ruff formatting --- .../multi-destination-exporting/multi_destination_logs.py | 4 +--- .../multi_destination_metrics.py | 8 ++++++-- .../multi_destination_traces.py | 4 +--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/examples/multi-destination-exporting/multi_destination_logs.py b/docs/examples/multi-destination-exporting/multi_destination_logs.py index 455f491d59..e41b467871 100644 --- a/docs/examples/multi-destination-exporting/multi_destination_logs.py +++ b/docs/examples/multi-destination-exporting/multi_destination_logs.py @@ -48,9 +48,7 @@ ) # Destination 2: OTLP over HTTP -http_exporter = HttpLogExporter( - endpoint="http://localhost:4318/v1/logs" -) +http_exporter = HttpLogExporter(endpoint="http://localhost:4318/v1/logs") logger_provider.add_log_record_processor( BatchLogRecordProcessor(http_exporter) ) diff --git a/docs/examples/multi-destination-exporting/multi_destination_metrics.py b/docs/examples/multi-destination-exporting/multi_destination_metrics.py index 264bf500e6..28f91bcf22 100644 --- a/docs/examples/multi-destination-exporting/multi_destination_metrics.py +++ b/docs/examples/multi-destination-exporting/multi_destination_metrics.py @@ -45,11 +45,15 @@ console_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) # Pass all readers to the MeterProvider -provider = MeterProvider(metric_readers=[grpc_reader, http_reader, console_reader]) +provider = MeterProvider( + metric_readers=[grpc_reader, http_reader, console_reader] +) metrics.set_meter_provider(provider) meter = metrics.get_meter(__name__) -counter = meter.create_counter("request.count", description="Number of requests") +counter = meter.create_counter( + "request.count", description="Number of requests" +) counter.add(1, {"endpoint": "/api/users"}) counter.add(1, {"endpoint": "/api/orders"}) diff --git a/docs/examples/multi-destination-exporting/multi_destination_traces.py b/docs/examples/multi-destination-exporting/multi_destination_traces.py index d1bed77b76..c94a1c7dde 100644 --- a/docs/examples/multi-destination-exporting/multi_destination_traces.py +++ b/docs/examples/multi-destination-exporting/multi_destination_traces.py @@ -41,9 +41,7 @@ provider.add_span_processor(BatchSpanProcessor(grpc_exporter)) # Destination 2: OTLP over HTTP -http_exporter = HttpSpanExporter( - endpoint="http://localhost:4318/v1/traces" -) +http_exporter = HttpSpanExporter(endpoint="http://localhost:4318/v1/traces") provider.add_span_processor(BatchSpanProcessor(http_exporter)) # Destination 3: Console (for debugging)