diff --git a/.github/workflows/release-udp-exporter.yml b/.github/workflows/release-udp-exporter.yml deleted file mode 100644 index 99dead871..000000000 --- a/.github/workflows/release-udp-exporter.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Release ADOT OTLP UDP Exporter - -on: - workflow_dispatch: - inputs: - version: - description: 'Version number for deployment e.g. 0.1.0' - required: true - type: string -env: - AWS_DEFAULT_REGION: us-east-1 - ARTIFACT_NAME: aws_otel_otlp_udp_exporter-${{ github.event.inputs.version }}-py3-none-any.whl - -permissions: - id-token: write - contents: write - -jobs: - build-test-publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install hatch pytest flask - - - name: Build package - working-directory: exporters/aws-otel-otlp-udp-exporter - run: hatch build - - - name: Download and run X-Ray Daemon - run: | - mkdir xray-daemon - cd xray-daemon - wget https://s3.us-west-2.amazonaws.com/aws-xray-assets.us-west-2/xray-daemon/aws-xray-daemon-linux-3.x.zip - unzip aws-xray-daemon-linux-3.x.zip - ./xray -o -n us-west-2 -f ./daemon-logs.log --log-level debug & - - - name: Install UDP Exporter - run: | - pip install ./exporters/aws-otel-otlp-udp-exporter/dist/*.whl - - - name: Ensure Unit Tests are passing - run: | - pytest exporters/aws-otel-otlp-udp-exporter/tests/ - - - name: Run Sample App in Background - working-directory: sample-applications/integ-test-app - run: | - # Start validation app - python udp_exporter_validation_app.py & - # Wait for validation app to initialize - sleep 5 - - - name: Call Sample App Endpoint - run: | - echo "traceId=$(curl localhost:8080/test)" >> $GITHUB_OUTPUT - - - name: Verify X-Ray daemon received traces - run: | - sleep 10 - echo "X-Ray daemon logs:" - cat xray-daemon/daemon-logs.log - - # Check if the daemon received and processed some data - if grep -q "sending.*batch" xray-daemon/daemon-logs.log; then - echo "✅ X-Ray daemon processed trace data (AWS upload errors are expected)" - exit 0 - elif grep -q "processor:.*segment" xray-daemon/daemon-logs.log; then - echo "✅ X-Ray daemon processed segment data (AWS upload errors are expected)" - exit 0 - else - echo "❌ No evidence of traces being received by X-Ray daemon" - exit 1 - fi - - - name: Configure AWS credentials for PyPI secrets - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ secrets.AWS_ROLE_ARN_SECRETS_MANAGER }} - aws-region: ${{ env.AWS_DEFAULT_REGION }} - - - name: Get PyPI secrets - uses: aws-actions/aws-secretsmanager-get-secrets@v1 - id: pypi_secrets - with: - secret-ids: | - PROD_PYPI_TOKEN,${{ secrets.PYPI_PROD_TOKEN_SECRET_ARN }} - TEST_PYPI_TOKEN,${{ secrets.PYPI_TEST_TOKEN_SECRET_ARN }} - parse-json-secrets: true - - - name: Install twine - run: pip install twine - - # The step below publishes to testpypi in order to catch any issues - # with the package configuration that would cause a failure to upload to PyPI. - - name: Publish to TestPyPI - env: - TWINE_USERNAME: '__token__' - TWINE_PASSWORD: ${{ env.TEST_PYPI_TOKEN_API_TOKEN }} - run: | - twine upload --repository testpypi --skip-existing --verbose exporters/aws-otel-otlp-udp-exporter/dist/${{ env.ARTIFACT_NAME }} - - # Publish to prod PyPI - - name: Publish to PyPI - env: - TWINE_USERNAME: '__token__' - TWINE_PASSWORD: ${{ env.PROD_PYPI_TOKEN_API_TOKEN }} - run: | - twine upload --skip-existing --verbose exporters/aws-otel-otlp-udp-exporter/dist/${{ env.ARTIFACT_NAME }} diff --git a/exporters/aws-otel-otlp-udp-exporter/README.rst b/exporters/aws-otel-otlp-udp-exporter/README.rst deleted file mode 100644 index 6d5e3d723..000000000 --- a/exporters/aws-otel-otlp-udp-exporter/README.rst +++ /dev/null @@ -1,17 +0,0 @@ -AWS OpenTelemetry OTLP UDP Exporter -=================================== - -Installation ------------- - -:: - - pip install aws-otel-otlp-udp-exporter - - -This package provides a UDP exporter for OpenTelemetry. - -References ----------- - -* `OpenTelemetry Project `_ diff --git a/exporters/aws-otel-otlp-udp-exporter/pyproject.toml b/exporters/aws-otel-otlp-udp-exporter/pyproject.toml deleted file mode 100644 index bff54cbf7..000000000 --- a/exporters/aws-otel-otlp-udp-exporter/pyproject.toml +++ /dev/null @@ -1,36 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "aws-otel-otlp-udp-exporter" -version = "0.1.0" -description = "OTLP UDP Exporter for OpenTelemetry" -readme = "README.rst" -license = "Apache-2.0" -requires-python = ">=3.8" -authors = [ - { name = "Amazon Web Services" } -] -classifiers = [ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", -] - -dependencies = [ - "opentelemetry-sdk == 1.27.0", - "opentelemetry-exporter-otlp-proto-common == 1.27.0", -] - -[project.urls] -Homepage = "https://github.com/aws-observability/aws-otel-python-instrumentation/tree/main/exporters/aws-otel-otlp-udp-exporter" - -[tool.hatch.build.targets.wheel] -packages = ["src/amazon"] diff --git a/exporters/aws-otel-otlp-udp-exporter/src/amazon/opentelemetry/exporters/otlp/udp/__init__.py b/exporters/aws-otel-otlp-udp-exporter/src/amazon/opentelemetry/exporters/otlp/udp/__init__.py deleted file mode 100644 index 1872b41ec..000000000 --- a/exporters/aws-otel-otlp-udp-exporter/src/amazon/opentelemetry/exporters/otlp/udp/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -from .exporter import ( - DEFAULT_ENDPOINT, - FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX, - FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX, - PROTOCOL_HEADER, - OTLPUdpSpanExporter, - UdpExporter, -) - -__all__ = [ - "UdpExporter", - "OTLPUdpSpanExporter", - "DEFAULT_ENDPOINT", - "FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX", - "FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX", - "PROTOCOL_HEADER", -] diff --git a/exporters/aws-otel-otlp-udp-exporter/src/amazon/opentelemetry/exporters/otlp/udp/exporter.py b/exporters/aws-otel-otlp-udp-exporter/src/amazon/opentelemetry/exporters/otlp/udp/exporter.py deleted file mode 100644 index 81b949801..000000000 --- a/exporters/aws-otel-otlp-udp-exporter/src/amazon/opentelemetry/exporters/otlp/udp/exporter.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -import base64 -import os -import socket -from logging import Logger, getLogger -from typing import Optional, Sequence, Tuple - -from typing_extensions import override - -from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans -from opentelemetry.sdk.trace import ReadableSpan -from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult - -DEFAULT_ENDPOINT = "127.0.0.1:2000" -PROTOCOL_HEADER = '{"format":"json","version":1}\n' - -FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX = "T1S" -FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX = "T1U" - -_logger: Logger = getLogger(__name__) - - -class UdpExporter: - def __init__(self, endpoint: Optional[str] = None): - self._endpoint = endpoint or DEFAULT_ENDPOINT - self._host, self._port = self._parse_endpoint(self._endpoint) - self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._socket.setblocking(False) - - def send_data(self, data: bytes, signal_format_prefix: str): - # base64 encoding and then converting to string with utf-8 - base64_encoded_string: str = base64.b64encode(data).decode("utf-8") - message = f"{PROTOCOL_HEADER}{signal_format_prefix}{base64_encoded_string}" - - try: - _logger.debug("Sending UDP data: %s", message) - self._socket.sendto(message.encode("utf-8"), (self._host, int(self._port))) - except Exception as exc: # pylint: disable=broad-except - _logger.error("Error sending UDP data: %s", exc) - raise - - def shutdown(self): - self._socket.close() - - # pylint: disable=no-self-use - def _parse_endpoint(self, endpoint: str) -> Tuple[str, int]: - try: - vals = endpoint.split(":") - host = vals[0] - port = int(vals[1]) - except Exception as exc: # pylint: disable=broad-except - raise ValueError(f"Invalid endpoint: {endpoint}") from exc - - return host, port - - -class OTLPUdpSpanExporter(SpanExporter): - def __init__(self, endpoint: Optional[str] = None, sampled: bool = True): - if endpoint is None and "AWS_LAMBDA_FUNCTION_NAME" in os.environ: - # If in an AWS Lambda Environment, `AWS_XRAY_DAEMON_ADDRESS` will be defined - endpoint = os.environ.get("AWS_XRAY_DAEMON_ADDRESS", DEFAULT_ENDPOINT) - - self._udp_exporter = UdpExporter(endpoint=endpoint) - self._sampled = sampled - - @override - def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: - serialized_data = encode_spans(spans).SerializeToString() - - try: - prefix = ( - FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX - if self._sampled - else FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX - ) - self._udp_exporter.send_data(data=serialized_data, signal_format_prefix=prefix) - return SpanExportResult.SUCCESS - except Exception as exc: # pylint: disable=broad-except - _logger.error("Error exporting spans: %s", exc) - return SpanExportResult.FAILURE - - # pylint: disable=no-self-use - @override - def force_flush(self, timeout_millis: int = 30000) -> bool: - # TODO: implement force flush - return True - - @override - def shutdown(self) -> None: - self._udp_exporter.shutdown() diff --git a/exporters/aws-otel-otlp-udp-exporter/tests/test_exporter.py b/exporters/aws-otel-otlp-udp-exporter/tests/test_exporter.py deleted file mode 100644 index 15514e14a..000000000 --- a/exporters/aws-otel-otlp-udp-exporter/tests/test_exporter.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -import base64 -import socket -import unittest -from unittest import TestCase -from unittest.mock import MagicMock, patch - -from amazon.opentelemetry.exporters.otlp.udp import ( - DEFAULT_ENDPOINT, - FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX, - FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX, - PROTOCOL_HEADER, - OTLPUdpSpanExporter, - UdpExporter, -) -from opentelemetry.sdk.trace.export import SpanExportResult - - -class TestUdpExporter(TestCase): - - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.socket.socket") - def test_udp_exporter_init_default(self, mock_socket): - exporter = UdpExporter() - self.assertEqual(exporter._endpoint, DEFAULT_ENDPOINT) - self.assertEqual(exporter._host, "127.0.0.1") - self.assertEqual(exporter._port, 2000) - mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM) - mock_socket().setblocking.assert_called_once_with(False) - - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.socket.socket") - def test_udp_exporter_init_with_endpoint(self, mock_socket): - exporter = UdpExporter(endpoint="localhost:5000") - self.assertNotEqual(exporter._endpoint, DEFAULT_ENDPOINT) - self.assertEqual(exporter._host, "localhost") - self.assertEqual(exporter._port, 5000) - mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM) - mock_socket().setblocking.assert_called_once_with(False) - - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.socket.socket") - def test_udp_exporter_init_invalid_endpoint(self, mock_socket): - with self.assertRaises(ValueError): - UdpExporter(endpoint="invalidEndpoint:port") - - # pylint: disable=no-self-use - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.socket.socket") - def test_send_data(self, mock_socket): - mock_socket_instance = mock_socket.return_value - exporter = UdpExporter() - input_bytes: bytes = b"hello" - encoded_bytes: bytes = base64.b64encode(input_bytes) - exporter.send_data(input_bytes, "signal_prefix") - expected_message = PROTOCOL_HEADER + "signal_prefix" + encoded_bytes.decode("utf-8") - mock_socket_instance.sendto.assert_called_once_with(expected_message.encode("utf-8"), ("127.0.0.1", 2000)) - - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.socket.socket") - def test_shutdown(self, mock_socket): - mock_socket_instance = mock_socket.return_value - exporter = UdpExporter() - exporter.shutdown() - mock_socket_instance.close.assert_called_once() - - -class TestOTLPUdpSpanExporter(unittest.TestCase): - - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.encode_spans") - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.UdpExporter") - def test_export_unsampled_span(self, mock_udp_exporter, mock_encode_spans): - mock_udp_exporter_instance = mock_udp_exporter.return_value - mock_encoded_data = MagicMock() - mock_encode_spans.return_value.SerializeToString.return_value = mock_encoded_data - exporter = OTLPUdpSpanExporter(sampled=False) - result = exporter.export(MagicMock()) - mock_udp_exporter_instance.send_data.assert_called_once_with( - data=mock_encoded_data, signal_format_prefix=FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX - ) - self.assertEqual(result, SpanExportResult.SUCCESS) - - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.encode_spans") - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.UdpExporter") - def test_export_sampled_span(self, mock_udp_exporter, mock_encode_spans): - mock_udp_exporter_instance = mock_udp_exporter.return_value - mock_encoded_data = MagicMock() - mock_encode_spans.return_value.SerializeToString.return_value = mock_encoded_data - exporter = OTLPUdpSpanExporter() - result = exporter.export(MagicMock()) - mock_udp_exporter_instance.send_data.assert_called_once_with( - data=mock_encoded_data, signal_format_prefix=FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX - ) - self.assertEqual(result, SpanExportResult.SUCCESS) - - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.encode_spans") - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.UdpExporter") - def test_export_with_exception(self, mock_udp_exporter, mock_encode_spans): - mock_udp_exporter_instance = mock_udp_exporter.return_value - mock_encoded_data = MagicMock() - mock_encode_spans.return_value.SerializeToString.return_value = mock_encoded_data - mock_udp_exporter_instance.send_data.side_effect = Exception("Something went wrong") - exporter = OTLPUdpSpanExporter() - result = exporter.export(MagicMock()) - self.assertEqual(result, SpanExportResult.FAILURE) - - # pylint: disable=no-self-use - @patch("amazon.opentelemetry.exporters.otlp.udp.exporter.UdpExporter") - def test_shutdown(self, mock_udp_exporter): - mock_udp_exporter_instance = mock_udp_exporter.return_value - exporter = OTLPUdpSpanExporter() - exporter.shutdown() - exporter.force_flush() - mock_udp_exporter_instance.shutdown.assert_called_once() diff --git a/sample-applications/integ-test-app/udp_exporter_validation_app.py b/sample-applications/integ-test-app/udp_exporter_validation_app.py deleted file mode 100644 index 23b3b7517..000000000 --- a/sample-applications/integ-test-app/udp_exporter_validation_app.py +++ /dev/null @@ -1,48 +0,0 @@ -from flask import Flask - -from amazon.opentelemetry.exporters.otlp.udp import OTLPUdpSpanExporter -from opentelemetry import trace -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor - -app = Flask(__name__) - -# Set up tracer provider -tracer_provider = TracerProvider() -trace.set_tracer_provider(tracer_provider) - -# Set up UDP exporter with batch processor -exporter = OTLPUdpSpanExporter(endpoint="127.0.0.1:2000") -span_processor = BatchSpanProcessor(exporter) -tracer_provider.add_span_processor(span_processor) - -# Get tracer -tracer = trace.get_tracer(__name__) - - -@app.route("/test", methods=["GET"]) -def create_trace(): - # Create a span for testing with various attributes - tracer = trace.get_tracer(__name__) - with tracer.start_as_current_span("test_parent_span") as parent: - parent.set_attribute("service.name", "validation-app") - parent.set_attribute("test.attribute", "test_value") - parent.add_event("test-event", {"event.data": "some data"}) - - # Get the trace ID - trace_id = format(parent.get_span_context().trace_id, "032x") - - # Add a child span - with tracer.start_as_current_span("test_child_span") as child: - child.set_attribute("child.attribute", "child_value") - print("Created spans with attributes and events") - - # Force flush to ensure spans are exported immediately - success = tracer_provider.force_flush() - print(f"Force flush {'succeeded' if success else 'failed'}") - - return trace_id - - -if __name__ == "__main__": - app.run(port=8080)