diff --git a/.github/workflows/lint_0.yml b/.github/workflows/lint_0.yml index a356157aec..964d9648b9 100644 --- a/.github/workflows/lint_0.yml +++ b/.github/workflows/lint_0.yml @@ -260,6 +260,25 @@ jobs: - name: Run tests run: tox -e lint-opentelemetry-exporter-otlp-proto-http + lint-opentelemetry-exporter-otlp-proto-kafka: + name: opentelemetry-exporter-otlp-proto-kafka + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.14 + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e lint-opentelemetry-exporter-otlp-proto-kafka + lint-opentelemetry-exporter-prometheus: name: opentelemetry-exporter-prometheus runs-on: ubuntu-latest diff --git a/.github/workflows/test_0.yml b/.github/workflows/test_0.yml index 68c07a957a..f3f7539e20 100644 --- a/.github/workflows/test_0.yml +++ b/.github/workflows/test_0.yml @@ -1780,6 +1780,139 @@ jobs: - name: Run tests run: tox -e pypy3-test-opentelemetry-exporter-otlp-proto-http -- -ra + py39-test-opentelemetry-exporter-otlp-proto-kafka_ubuntu-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.9 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: "3.9" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py39-test-opentelemetry-exporter-otlp-proto-kafka -- -ra + + py310-test-opentelemetry-exporter-otlp-proto-kafka_ubuntu-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.10 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py310-test-opentelemetry-exporter-otlp-proto-kafka -- -ra + + py311-test-opentelemetry-exporter-otlp-proto-kafka_ubuntu-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.11 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py311-test-opentelemetry-exporter-otlp-proto-kafka -- -ra + + py312-test-opentelemetry-exporter-otlp-proto-kafka_ubuntu-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.12 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py312-test-opentelemetry-exporter-otlp-proto-kafka -- -ra + + py313-test-opentelemetry-exporter-otlp-proto-kafka_ubuntu-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.13 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py313-test-opentelemetry-exporter-otlp-proto-kafka -- -ra + + py314-test-opentelemetry-exporter-otlp-proto-kafka_ubuntu-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.14 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.14 + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py314-test-opentelemetry-exporter-otlp-proto-kafka -- -ra + + pypy3-test-opentelemetry-exporter-otlp-proto-kafka_ubuntu-latest: + name: opentelemetry-exporter-otlp-proto-kafka pypy-3.9 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python pypy-3.9 + uses: actions/setup-python@v5 + with: + python-version: "pypy-3.9" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e pypy3-test-opentelemetry-exporter-otlp-proto-kafka -- -ra + py39-test-opentelemetry-exporter-prometheus_ubuntu-latest: name: opentelemetry-exporter-prometheus 3.9 Ubuntu runs-on: ubuntu-latest @@ -4735,8 +4868,8 @@ jobs: - name: Run tests run: tox -e pypy3-test-opentelemetry-exporter-otlp-proto-http -- -ra - py39-test-opentelemetry-exporter-prometheus_windows-latest: - name: opentelemetry-exporter-prometheus 3.9 Windows + py39-test-opentelemetry-exporter-otlp-proto-kafka_windows-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.9 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -4755,10 +4888,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py39-test-opentelemetry-exporter-prometheus -- -ra + run: tox -e py39-test-opentelemetry-exporter-otlp-proto-kafka -- -ra - py310-test-opentelemetry-exporter-prometheus_windows-latest: - name: opentelemetry-exporter-prometheus 3.10 Windows + py310-test-opentelemetry-exporter-otlp-proto-kafka_windows-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.10 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -4777,10 +4910,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py310-test-opentelemetry-exporter-prometheus -- -ra + run: tox -e py310-test-opentelemetry-exporter-otlp-proto-kafka -- -ra - py311-test-opentelemetry-exporter-prometheus_windows-latest: - name: opentelemetry-exporter-prometheus 3.11 Windows + py311-test-opentelemetry-exporter-otlp-proto-kafka_windows-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.11 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -4799,10 +4932,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py311-test-opentelemetry-exporter-prometheus -- -ra + run: tox -e py311-test-opentelemetry-exporter-otlp-proto-kafka -- -ra - py312-test-opentelemetry-exporter-prometheus_windows-latest: - name: opentelemetry-exporter-prometheus 3.12 Windows + py312-test-opentelemetry-exporter-otlp-proto-kafka_windows-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.12 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -4821,10 +4954,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py312-test-opentelemetry-exporter-prometheus -- -ra + run: tox -e py312-test-opentelemetry-exporter-otlp-proto-kafka -- -ra - py313-test-opentelemetry-exporter-prometheus_windows-latest: - name: opentelemetry-exporter-prometheus 3.13 Windows + py313-test-opentelemetry-exporter-otlp-proto-kafka_windows-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.13 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -4843,10 +4976,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py313-test-opentelemetry-exporter-prometheus -- -ra + run: tox -e py313-test-opentelemetry-exporter-otlp-proto-kafka -- -ra - py314-test-opentelemetry-exporter-prometheus_windows-latest: - name: opentelemetry-exporter-prometheus 3.14 Windows + py314-test-opentelemetry-exporter-otlp-proto-kafka_windows-latest: + name: opentelemetry-exporter-otlp-proto-kafka 3.14 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -4865,10 +4998,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py314-test-opentelemetry-exporter-prometheus -- -ra + run: tox -e py314-test-opentelemetry-exporter-otlp-proto-kafka -- -ra - pypy3-test-opentelemetry-exporter-prometheus_windows-latest: - name: opentelemetry-exporter-prometheus pypy-3.9 Windows + pypy3-test-opentelemetry-exporter-otlp-proto-kafka_windows-latest: + name: opentelemetry-exporter-otlp-proto-kafka pypy-3.9 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -4887,164 +5020,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e pypy3-test-opentelemetry-exporter-prometheus -- -ra - - py39-test-opentelemetry-exporter-zipkin-combined_windows-latest: - name: opentelemetry-exporter-zipkin-combined 3.9 Windows - runs-on: windows-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python 3.9 - uses: actions/setup-python@v5 - with: - python-version: "3.9" - - - name: Install tox - run: pip install tox-uv + run: tox -e pypy3-test-opentelemetry-exporter-otlp-proto-kafka -- -ra - - name: Configure git to support long filenames - run: git config --system core.longpaths true - - - name: Run tests - run: tox -e py39-test-opentelemetry-exporter-zipkin-combined -- -ra - - py310-test-opentelemetry-exporter-zipkin-combined_windows-latest: - name: opentelemetry-exporter-zipkin-combined 3.10 Windows - runs-on: windows-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Install tox - run: pip install tox-uv - - - name: Configure git to support long filenames - run: git config --system core.longpaths true - - - name: Run tests - run: tox -e py310-test-opentelemetry-exporter-zipkin-combined -- -ra - - py311-test-opentelemetry-exporter-zipkin-combined_windows-latest: - name: opentelemetry-exporter-zipkin-combined 3.11 Windows - runs-on: windows-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install tox - run: pip install tox-uv - - - name: Configure git to support long filenames - run: git config --system core.longpaths true - - - name: Run tests - run: tox -e py311-test-opentelemetry-exporter-zipkin-combined -- -ra - - py312-test-opentelemetry-exporter-zipkin-combined_windows-latest: - name: opentelemetry-exporter-zipkin-combined 3.12 Windows - runs-on: windows-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install tox - run: pip install tox-uv - - - name: Configure git to support long filenames - run: git config --system core.longpaths true - - - name: Run tests - run: tox -e py312-test-opentelemetry-exporter-zipkin-combined -- -ra - - py313-test-opentelemetry-exporter-zipkin-combined_windows-latest: - name: opentelemetry-exporter-zipkin-combined 3.13 Windows - runs-on: windows-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python 3.13 - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - - name: Install tox - run: pip install tox-uv - - - name: Configure git to support long filenames - run: git config --system core.longpaths true - - - name: Run tests - run: tox -e py313-test-opentelemetry-exporter-zipkin-combined -- -ra - - py314-test-opentelemetry-exporter-zipkin-combined_windows-latest: - name: opentelemetry-exporter-zipkin-combined 3.14 Windows - runs-on: windows-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python 3.14 - uses: actions/setup-python@v5 - with: - python-version: "3.14" - - - name: Install tox - run: pip install tox-uv - - - name: Configure git to support long filenames - run: git config --system core.longpaths true - - - name: Run tests - run: tox -e py314-test-opentelemetry-exporter-zipkin-combined -- -ra - - pypy3-test-opentelemetry-exporter-zipkin-combined_windows-latest: - name: opentelemetry-exporter-zipkin-combined pypy-3.9 Windows - runs-on: windows-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python pypy-3.9 - uses: actions/setup-python@v5 - with: - python-version: "pypy-3.9" - - - name: Install tox - run: pip install tox-uv - - - name: Configure git to support long filenames - run: git config --system core.longpaths true - - - name: Run tests - run: tox -e pypy3-test-opentelemetry-exporter-zipkin-combined -- -ra - - py39-test-opentelemetry-exporter-zipkin-proto-http_windows-latest: - name: opentelemetry-exporter-zipkin-proto-http 3.9 Windows + py39-test-opentelemetry-exporter-prometheus_windows-latest: + name: opentelemetry-exporter-prometheus 3.9 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -5063,10 +5042,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py39-test-opentelemetry-exporter-zipkin-proto-http -- -ra + run: tox -e py39-test-opentelemetry-exporter-prometheus -- -ra - py310-test-opentelemetry-exporter-zipkin-proto-http_windows-latest: - name: opentelemetry-exporter-zipkin-proto-http 3.10 Windows + py310-test-opentelemetry-exporter-prometheus_windows-latest: + name: opentelemetry-exporter-prometheus 3.10 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -5085,10 +5064,10 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py310-test-opentelemetry-exporter-zipkin-proto-http -- -ra + run: tox -e py310-test-opentelemetry-exporter-prometheus -- -ra - py311-test-opentelemetry-exporter-zipkin-proto-http_windows-latest: - name: opentelemetry-exporter-zipkin-proto-http 3.11 Windows + py311-test-opentelemetry-exporter-prometheus_windows-latest: + name: opentelemetry-exporter-prometheus 3.11 Windows runs-on: windows-latest timeout-minutes: 30 steps: @@ -5107,4 +5086,4 @@ jobs: run: git config --system core.longpaths true - name: Run tests - run: tox -e py311-test-opentelemetry-exporter-zipkin-proto-http -- -ra + run: tox -e py311-test-opentelemetry-exporter-prometheus -- -ra diff --git a/.github/workflows/test_1.yml b/.github/workflows/test_1.yml index f3c698e7d3..876d9f8e2d 100644 --- a/.github/workflows/test_1.yml +++ b/.github/workflows/test_1.yml @@ -32,6 +32,314 @@ env: jobs: + py312-test-opentelemetry-exporter-prometheus_windows-latest: + name: opentelemetry-exporter-prometheus 3.12 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py312-test-opentelemetry-exporter-prometheus -- -ra + + py313-test-opentelemetry-exporter-prometheus_windows-latest: + name: opentelemetry-exporter-prometheus 3.13 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py313-test-opentelemetry-exporter-prometheus -- -ra + + py314-test-opentelemetry-exporter-prometheus_windows-latest: + name: opentelemetry-exporter-prometheus 3.14 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.14 + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py314-test-opentelemetry-exporter-prometheus -- -ra + + pypy3-test-opentelemetry-exporter-prometheus_windows-latest: + name: opentelemetry-exporter-prometheus pypy-3.9 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python pypy-3.9 + uses: actions/setup-python@v5 + with: + python-version: "pypy-3.9" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e pypy3-test-opentelemetry-exporter-prometheus -- -ra + + py39-test-opentelemetry-exporter-zipkin-combined_windows-latest: + name: opentelemetry-exporter-zipkin-combined 3.9 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: "3.9" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py39-test-opentelemetry-exporter-zipkin-combined -- -ra + + py310-test-opentelemetry-exporter-zipkin-combined_windows-latest: + name: opentelemetry-exporter-zipkin-combined 3.10 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py310-test-opentelemetry-exporter-zipkin-combined -- -ra + + py311-test-opentelemetry-exporter-zipkin-combined_windows-latest: + name: opentelemetry-exporter-zipkin-combined 3.11 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py311-test-opentelemetry-exporter-zipkin-combined -- -ra + + py312-test-opentelemetry-exporter-zipkin-combined_windows-latest: + name: opentelemetry-exporter-zipkin-combined 3.12 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py312-test-opentelemetry-exporter-zipkin-combined -- -ra + + py313-test-opentelemetry-exporter-zipkin-combined_windows-latest: + name: opentelemetry-exporter-zipkin-combined 3.13 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py313-test-opentelemetry-exporter-zipkin-combined -- -ra + + py314-test-opentelemetry-exporter-zipkin-combined_windows-latest: + name: opentelemetry-exporter-zipkin-combined 3.14 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.14 + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py314-test-opentelemetry-exporter-zipkin-combined -- -ra + + pypy3-test-opentelemetry-exporter-zipkin-combined_windows-latest: + name: opentelemetry-exporter-zipkin-combined pypy-3.9 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python pypy-3.9 + uses: actions/setup-python@v5 + with: + python-version: "pypy-3.9" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e pypy3-test-opentelemetry-exporter-zipkin-combined -- -ra + + py39-test-opentelemetry-exporter-zipkin-proto-http_windows-latest: + name: opentelemetry-exporter-zipkin-proto-http 3.9 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: "3.9" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py39-test-opentelemetry-exporter-zipkin-proto-http -- -ra + + py310-test-opentelemetry-exporter-zipkin-proto-http_windows-latest: + name: opentelemetry-exporter-zipkin-proto-http 3.10 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py310-test-opentelemetry-exporter-zipkin-proto-http -- -ra + + py311-test-opentelemetry-exporter-zipkin-proto-http_windows-latest: + name: opentelemetry-exporter-zipkin-proto-http 3.11 Windows + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install tox + run: pip install tox-uv + + - name: Configure git to support long filenames + run: git config --system core.longpaths true + + - name: Run tests + run: tox -e py311-test-opentelemetry-exporter-zipkin-proto-http -- -ra + py312-test-opentelemetry-exporter-zipkin-proto-http_windows-latest: name: opentelemetry-exporter-zipkin-proto-http 3.12 Windows runs-on: windows-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index e3091794e0..d35901bffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Added OTLP Kafka exporter + ([#4841](https://github.com/open-telemetry/opentelemetry-python/pull/4841)) - `opentelemetry-sdk`: Fix the type hint of the `_metrics_data` property to allow `None` ([#4837](https://github.com/open-telemetry/opentelemetry-python/pull/4837) - Regenerate opentelemetry-proto code with v1.9.0 release diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/LICENSE b/exporter/opentelemetry-exporter-otlp-proto-kafka/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/README.rst b/exporter/opentelemetry-exporter-otlp-proto-kafka/README.rst new file mode 100644 index 0000000000..6da1c18144 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/README.rst @@ -0,0 +1,25 @@ +OpenTelemetry Collector Protobuf over Kafka Exporter +==================================================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-exporter-otlp-proto-kafka.svg + :target: https://pypi.org/project/opentelemetry-exporter-otlp-proto-kafka/ + +This library allows to export data to the OpenTelemetry Collector using the OpenTelemetry Protocol using Protobuf over Kafka. + +Installation +------------ + +:: + + pip install opentelemetry-exporter-otlp-proto-kafka + + +References +---------- + +* `OpenTelemetry Collector Exporter `_ +* `OpenTelemetry Collector `_ +* `OpenTelemetry `_ +* `OpenTelemetry Protocol Specification `_ diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/pyproject.toml b/exporter/opentelemetry-exporter-otlp-proto-kafka/pyproject.toml new file mode 100644 index 0000000000..028bf97172 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "opentelemetry-exporter-otlp-proto-kafka" +dynamic = ["version"] +description = "OpenTelemetry Collector Protobuf over Kafka Exporter" +readme = "README.rst" +license = "Apache-2.0" +requires-python = ">=3.9" +authors = [ + { name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Framework :: OpenTelemetry", + "Framework :: OpenTelemetry :: Exporters", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "googleapis-common-protos ~= 1.52", + "opentelemetry-api ~= 1.15", + "opentelemetry-proto == 1.40.0.dev", + "opentelemetry-sdk ~= 1.40.0.dev", + "opentelemetry-exporter-otlp-proto-common == 1.40.0.dev", + "kafka-python ~= 2.3.0", +] + +[project.entry-points.opentelemetry_traces_exporter] +otlp_proto_kafka = "opentelemetry.exporter.otlp.proto.kafka.trace_exporter:OTLPSpanExporter" + +[project.entry-points.opentelemetry_metrics_exporter] +otlp_proto_kafka = "opentelemetry.exporter.otlp.proto.kafka.metric_exporter:OTLPMetricExporter" + +[project.entry-points.opentelemetry_logs_exporter] +otlp_proto_kafka = "opentelemetry.exporter.otlp.proto.kafka._log_exporter:OTLPLogExporter" + +[project.urls] +Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/exporter/opentelemetry-exporter-otlp-proto-kafka" +Repository = "https://github.com/open-telemetry/opentelemetry-python" + + +[tool.hatch.version] +path = "src/opentelemetry/exporter/otlp/proto/kafka/version/__init__.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/src", + "/tests", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/opentelemetry"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/__init__.py new file mode 100644 index 0000000000..ce55671ae9 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/__init__.py @@ -0,0 +1,68 @@ +# 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 library allows to export tracing data to an OTLP collector. + +Usage +----- + +The **OTLP Span Exporter** allows to export `OpenTelemetry`_ traces to the +`OTLP`_ collector. + +You can configure the exporter with the following environment variables: + +- :envvar:`OTEL_EXPORTER_KAFKA_LOGS_TOPIC` +- :envvar:`OTEL_EXPORTER_KAFKA_METRICS_TOPIC` +- :envvar:`OTEL_EXPORTER_KAFKA_TRACES_TOPIC` +- :envvar:`OTEL_EXPORTER_OTLP_LOGS_HEADERS` +- :envvar:`OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` +- :envvar:`OTEL_EXPORTER_OTLP_METRICS_HEADERS` +- :envvar:`OTEL_EXPORTER_OTLP_METRICS_TIMEOUT` +- :envvar:`OTEL_EXPORTER_OTLP_TRACES_HEADERS` +- :envvar:`OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` + +.. _OTLP: https://github.com/open-telemetry/opentelemetry-collector/ +.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ + +.. code:: python + + from opentelemetry import trace + from opentelemetry.exporter.otlp.proto.kafka.trace_exporter import OTLPSpanExporter + from opentelemetry.sdk.resources import Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + + # Resource can be required for some backends, e.g. Jaeger + # If resource wouldn't be set - traces wouldn't appears in Jaeger + resource = Resource(attributes={ + "service.name": "service" + }) + + trace.set_tracer_provider(TracerProvider(resource=resource)) + tracer = trace.get_tracer(__name__) + + otlp_exporter = OTLPSpanExporter() + + span_processor = BatchSpanProcessor(otlp_exporter) + + trace.get_tracer_provider().add_span_processor(span_processor) + + with tracer.start_as_current_span("foo"): + print("Hello world!") + +API +--- +""" diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/common.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/common.py new file mode 100644 index 0000000000..90a88ab19c --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/common.py @@ -0,0 +1,62 @@ +from os import environ +from typing import Final, Mapping, Optional, Sequence, Tuple + +from kafka import KafkaProducer +from kafka.errors import KafkaTimeoutError +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_HEADERS, + OTEL_EXPORTER_OTLP_TIMEOUT, +) +from opentelemetry.util.re import parse_env_headers + +DEFAULT_TIMEOUT: Final[int] = 10 +DEFAULT_CLIENT_ID: Final[str] = "otel-exporter" +DEFAULT_BROKERS: Final[str] = "localhost:9092" + + +def publish_serialized_data( + producer: KafkaProducer, + topic: str, + serialized_data: bytes, + headers: Sequence[tuple[str, bytes]], + timeout: float, +) -> None: + future = producer.send( + topic, + value=serialized_data, + headers=headers, + ) + future.get(timeout) + + +def flush_producer(producer: KafkaProducer, timeout_millis: float) -> bool: + try: + producer.flush(timeout_millis / 1000) + return True + except KafkaTimeoutError: + return False + + +def timeout_from_env( + exporter_environment_variable_name: str, timeout: Optional[float] +) -> float: + return timeout or float( + environ.get( + exporter_environment_variable_name, + environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, DEFAULT_TIMEOUT), + ) + ) + + +def headers_from_env( + exporter_environment_variable_name: str, + headers: Optional[Mapping[str, str]], +) -> Sequence[Tuple[str, bytes]]: + headers_string = environ.get( + exporter_environment_variable_name, + environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""), + ) + mapping_headers = headers or parse_env_headers( + headers_string, liberal=True + ) + return [(key, value.encode()) for key, value in mapping_headers.items()] diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/log_exporter.py new file mode 100644 index 0000000000..542e2bcbd3 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/log_exporter.py @@ -0,0 +1,117 @@ +# 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. + +import logging +from os import environ +from typing import Final, Mapping, Optional, Sequence + +from kafka import KafkaProducer +from kafka.errors import KafkaError +from opentelemetry.exporter.otlp.proto.common._log_encoder import encode_logs +from opentelemetry.exporter.otlp.proto.kafka._internal.common import ( + DEFAULT_BROKERS, + DEFAULT_CLIENT_ID, + flush_producer, + headers_from_env, + publish_serialized_data, + timeout_from_env, +) +from opentelemetry.sdk._logs import ReadableLogRecord +from opentelemetry.sdk._logs.export import ( + LogRecordExporter, + LogRecordExportResult, +) +from opentelemetry.sdk._shared_internal import DuplicateFilter +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_KAFKA_BROKERS, + OTEL_EXPORTER_KAFKA_CLIENT_ID, + OTEL_EXPORTER_KAFKA_LOGS_TOPIC, + OTEL_EXPORTER_OTLP_LOGS_HEADERS, + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, +) + +logger = logging.getLogger(__name__) +# This prevents logs generated when a log fails to be written to generate another log which fails to be written etc. etc. +logger.addFilter(DuplicateFilter()) + + +DEFAULT_TOPIC: Final[str] = "otlp_logs" + + +class OTLPLogExporter(LogRecordExporter): + def __init__( + self, + brokers: Optional[str] = None, + client_id: Optional[str] = None, + topic: Optional[str] = None, + headers: Optional[Mapping[str, str]] = None, + timeout: Optional[float] = None, + producer: Optional[KafkaProducer] = None, + ): + self._brokers = brokers or environ.get( + OTEL_EXPORTER_KAFKA_BROKERS, DEFAULT_BROKERS + ) + self._client_id = client_id or environ.get( + OTEL_EXPORTER_KAFKA_CLIENT_ID, DEFAULT_CLIENT_ID + ) + self._topic = topic or environ.get( + OTEL_EXPORTER_KAFKA_LOGS_TOPIC, DEFAULT_TOPIC + ) + self._headers = headers_from_env( + OTEL_EXPORTER_OTLP_LOGS_HEADERS, headers + ) + self._timeout = timeout_from_env( + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, timeout + ) + + self._producer = producer or KafkaProducer( + bootstrap_servers=self._brokers, + client_id=self._client_id, + ) + + self._shutdown = False + + def export( + self, batch: Sequence[ReadableLogRecord] + ) -> LogRecordExportResult: + if self._shutdown: + logger.warning("Exporter already shutdown, ignoring batch") + return LogRecordExportResult.FAILURE + + serialized_data = encode_logs(batch).SerializeToString() + try: + publish_serialized_data( + self._producer, + self._topic, + serialized_data, + self._headers, + self._timeout, + ) + return LogRecordExportResult.SUCCESS + except KafkaError as e: + logger.error( + "Failed to export logs batch reason: %s", + e, + ) + return LogRecordExportResult.FAILURE + + def shutdown(self): + if self._shutdown: + logger.warning("Exporter already shutdown, ignoring call") + return + self._shutdown = True + self._producer.close() + + def force_flush(self, timeout_millis: float = 10_000) -> bool: + return flush_producer(self._producer, timeout_millis) diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/metric_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/metric_exporter.py new file mode 100644 index 0000000000..7cf8025b18 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/metric_exporter.py @@ -0,0 +1,134 @@ +# 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. +from __future__ import annotations + +import logging +from os import environ +from typing import ( + Final, + Optional, +) + +from kafka import KafkaProducer +from kafka.errors import KafkaError +from opentelemetry.exporter.otlp.proto.common._internal.metrics_encoder import ( + OTLPMetricExporterMixin, +) +from opentelemetry.exporter.otlp.proto.common.metrics_encoder import ( + encode_metrics, +) +from opentelemetry.exporter.otlp.proto.kafka._internal.common import ( + DEFAULT_BROKERS, + DEFAULT_CLIENT_ID, + flush_producer, + headers_from_env, + publish_serialized_data, + timeout_from_env, +) +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_KAFKA_BROKERS, + OTEL_EXPORTER_KAFKA_CLIENT_ID, + OTEL_EXPORTER_KAFKA_METRICS_TOPIC, + OTEL_EXPORTER_OTLP_METRICS_HEADERS, + OTEL_EXPORTER_OTLP_METRICS_TIMEOUT, +) +from opentelemetry.sdk.metrics._internal.aggregation import Aggregation +from opentelemetry.sdk.metrics.export import ( + AggregationTemporality, + MetricExporter, + MetricExportResult, + MetricsData, +) + +logger = logging.getLogger(__name__) + + +DEFAULT_TOPIC: Final[str] = "otlp_metrics" + + +class OTLPMetricExporter(MetricExporter, OTLPMetricExporterMixin): + def __init__( + self, + brokers: str | None = None, + client_id: str | None = None, + topic: str | None = None, + headers: dict[str, str] | None = None, + timeout: float | None = None, + preferred_temporality: dict[type, AggregationTemporality] + | None = None, + preferred_aggregation: dict[type, Aggregation] | None = None, + producer: Optional[KafkaProducer] = None, + ): + self._brokers = brokers or environ.get( + OTEL_EXPORTER_KAFKA_BROKERS, DEFAULT_BROKERS + ) + self._client_id = client_id or environ.get( + OTEL_EXPORTER_KAFKA_CLIENT_ID, DEFAULT_CLIENT_ID + ) + self._topic = topic or environ.get( + OTEL_EXPORTER_KAFKA_METRICS_TOPIC, DEFAULT_TOPIC + ) + self._headers = headers_from_env( + OTEL_EXPORTER_OTLP_METRICS_HEADERS, headers + ) + self._timeout = timeout_from_env( + OTEL_EXPORTER_OTLP_METRICS_TIMEOUT, timeout + ) + + self._producer = producer or KafkaProducer( + bootstrap_servers=self._brokers, + client_id=self._client_id, + ) + + self._shutdown = False + + self._common_configuration( + preferred_temporality, preferred_aggregation + ) + self._shutdown = False + + def export( + self, + metrics_data: MetricsData, + timeout_millis: Optional[float] = 10000, + **kwargs, + ) -> MetricExportResult: + if self._shutdown: + logger.warning("Exporter already shutdown, ignoring batch") + return MetricExportResult.FAILURE + serialized_data = encode_metrics(metrics_data).SerializeToString() + try: + publish_serialized_data( + self._producer, + self._topic, + serialized_data, + self._headers, + self._timeout, + ) + return MetricExportResult.SUCCESS + except KafkaError as e: + logger.error( + "Failed to export metrics batch reason: %s", + e, + ) + return MetricExportResult.FAILURE + + def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None: + if self._shutdown: + logger.warning("Exporter already shutdown, ignoring call") + return + self._shutdown = True + self._producer.close() + + def force_flush(self, timeout_millis: float = 10_000) -> bool: + return flush_producer(self._producer, timeout_millis) diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/trace_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/trace_exporter.py new file mode 100644 index 0000000000..321366a535 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_internal/trace_exporter.py @@ -0,0 +1,111 @@ +# 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. + +import logging +from os import environ +from typing import Dict, Final, Optional, Sequence + +from kafka import KafkaProducer +from kafka.errors import KafkaError +from opentelemetry.exporter.otlp.proto.common.trace_encoder import ( + encode_spans, +) +from opentelemetry.exporter.otlp.proto.kafka._internal.common import ( + DEFAULT_BROKERS, + DEFAULT_CLIENT_ID, + flush_producer, + headers_from_env, + publish_serialized_data, + timeout_from_env, +) +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_KAFKA_BROKERS, + OTEL_EXPORTER_KAFKA_CLIENT_ID, + OTEL_EXPORTER_KAFKA_TRACES_TOPIC, + OTEL_EXPORTER_OTLP_TRACES_HEADERS, + OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, +) +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult + +logger = logging.getLogger(__name__) + + +DEFAULT_TOPIC: Final[str] = "otlp_spans" + + +class OTLPSpanExporter(SpanExporter): + def __init__( + self, + brokers: Optional[str] = None, + client_id: Optional[str] = None, + topic: Optional[str] = None, + headers: Optional[Dict[str, str]] = None, + timeout: Optional[float] = None, + producer: Optional[KafkaProducer] = None, + ): + self._brokers = brokers or environ.get( + OTEL_EXPORTER_KAFKA_BROKERS, DEFAULT_BROKERS + ) + self._client_id = client_id or environ.get( + OTEL_EXPORTER_KAFKA_CLIENT_ID, DEFAULT_CLIENT_ID + ) + self._topic = topic or environ.get( + OTEL_EXPORTER_KAFKA_TRACES_TOPIC, DEFAULT_TOPIC + ) + self._headers = headers_from_env( + OTEL_EXPORTER_OTLP_TRACES_HEADERS, headers + ) + self._timeout = timeout_from_env( + OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, timeout + ) + + self._producer = producer or KafkaProducer( + bootstrap_servers=self._brokers, + client_id=self._client_id, + ) + + self._shutdown = False + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + if self._shutdown: + logger.warning("Exporter already shutdown, ignoring batch") + return SpanExportResult.FAILURE + + serialized_data = encode_spans(spans).SerializePartialToString() + try: + publish_serialized_data( + self._producer, + self._topic, + serialized_data, + self._headers, + self._timeout, + ) + return SpanExportResult.SUCCESS + except KafkaError as e: + logger.error( + "Failed to export span batch reason: %s", + e, + ) + return SpanExportResult.FAILURE + + def shutdown(self): + if self._shutdown: + logger.warning("Exporter already shutdown, ignoring call") + return + self._shutdown = True + self._producer.close() + + def force_flush(self, timeout_millis: int = 30000) -> bool: + return flush_producer(self._producer, timeout_millis) diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_log_exporter.py new file mode 100644 index 0000000000..f6d906106d --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/_log_exporter.py @@ -0,0 +1,19 @@ +# 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. + +from opentelemetry.exporter.otlp.proto.kafka._internal.log_exporter import ( + OTLPLogExporter, +) + +__all__ = ["OTLPLogExporter"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/metric_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/metric_exporter.py new file mode 100644 index 0000000000..619e40a27c --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/metric_exporter.py @@ -0,0 +1,18 @@ +# 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. + +from opentelemetry.exporter.otlp.proto.kafka._internal.metric_exporter import ( + OTLPMetricExporter, +) + +__all__ = ["OTLPMetricExporter"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/py.typed b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/trace_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/trace_exporter.py new file mode 100644 index 0000000000..8f420ddbc9 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/trace_exporter.py @@ -0,0 +1,19 @@ +# 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. + +from opentelemetry.exporter.otlp.proto.kafka._internal.trace_exporter import ( + OTLPSpanExporter, +) + +__all__ = ["OTLPSpanExporter"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/version/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/version/__init__.py new file mode 100644 index 0000000000..fa0beb9a1b --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/src/opentelemetry/exporter/otlp/proto/kafka/version/__init__.py @@ -0,0 +1,15 @@ +# 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. + +__version__ = "1.40.0.dev" diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/test-requirements.txt b/exporter/opentelemetry-exporter-otlp-proto-kafka/test-requirements.txt new file mode 100644 index 0000000000..58b42d0300 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/test-requirements.txt @@ -0,0 +1,12 @@ +googleapis-common-protos==1.68.0 +importlib-metadata==8.6.1 +kafka-python==2.3.0 +protobuf==5.29.3 +zipp==3.21.0 +pytest==7.4.4 +-e opentelemetry-api +-e exporter/opentelemetry-exporter-otlp-proto-common +-e exporter/opentelemetry-exporter-otlp-proto-kafka +-e opentelemetry-proto +-e opentelemetry-sdk +-e opentelemetry-semantic-conventions diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_log_exporter.py new file mode 100644 index 0000000000..ea10ddca17 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_log_exporter.py @@ -0,0 +1,145 @@ +# 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. + + +import unittest +from logging import ERROR, WARNING +from unittest.mock import Mock, create_autospec, patch + +from kafka import KafkaProducer +from kafka.errors import InvalidRequiredAcksError, KafkaTimeoutError + +from opentelemetry.exporter.otlp.proto.kafka._internal import log_exporter +from opentelemetry.exporter.otlp.proto.kafka._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.sdk._logs._internal.export import LogRecordExportResult +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_KAFKA_LOGS_TOPIC, +) + + +class TestOTLPLogExporter(unittest.TestCase): + def setUp(self): + self.mock_producer = create_autospec(KafkaProducer) + + @patch.object(log_exporter, "encode_logs") + def test_export(self, mock_encode_logs): + mock_spans = Mock() + self.assertEqual( + OTLPLogExporter(producer=self.mock_producer).export(mock_spans), + LogRecordExportResult.SUCCESS, + ) + + mock_encode_logs.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "otlp_logs", + value=mock_encode_logs.return_value.SerializeToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once_with(10) + + @patch.dict( + "os.environ", + {OTEL_EXPORTER_KAFKA_LOGS_TOPIC: "logs_topic"}, + ) + @patch.object(log_exporter, "encode_logs") + def test_export_env_logs_topic(self, mock_encode_logs): + mock_spans = Mock() + self.assertEqual( + OTLPLogExporter(producer=self.mock_producer).export(mock_spans), + LogRecordExportResult.SUCCESS, + ) + + mock_encode_logs.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "logs_topic", + value=mock_encode_logs.return_value.SerializeToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once_with(10) + + @patch.object(log_exporter, "encode_logs") + def test_export_constructor_logs_topic(self, mock_encode_logs): + mock_spans = Mock() + self.assertEqual( + OTLPLogExporter( + producer=self.mock_producer, topic="logs_topic" + ).export(mock_spans), + LogRecordExportResult.SUCCESS, + ) + + mock_encode_logs.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "logs_topic", + value=mock_encode_logs.return_value.SerializeToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once_with(10) + + def test_shutdown(self): + OTLPLogExporter(producer=self.mock_producer).shutdown() + + self.mock_producer.close.assert_called_once() + + @patch.object(log_exporter, "encode_logs") + def test_export_kafka_error(self, mock_encode_logs): + self.mock_producer.send.side_effect = InvalidRequiredAcksError() + with self.assertLogs(level=ERROR) as error: + self.assertEqual( + OTLPLogExporter(producer=self.mock_producer).export(Mock()), + LogRecordExportResult.FAILURE, + ) + self.assertIn( + "Failed to export logs batch reason:", + error.records[0].message, + ) + + def test_export_post_shutdown_fails(self): + exporter = OTLPLogExporter(producer=self.mock_producer) + exporter.shutdown() + + with self.assertLogs(level=WARNING) as warning: + self.assertEqual( + exporter.export(Mock()), + LogRecordExportResult.FAILURE, + ) + self.assertIn( + "Exporter already shutdown, ignoring batch", + warning.records[0].message, + ) + + def test_multiple_shutdown_warning(self): + exporter = OTLPLogExporter(producer=self.mock_producer) + exporter.shutdown() + + with self.assertLogs(level=WARNING) as warning: + exporter.shutdown() + self.assertIn( + "Exporter already shutdown, ignoring call", + warning.records[0].message, + ) + + def test_force_flush(self): + self.assertTrue( + OTLPLogExporter(producer=self.mock_producer).force_flush(), + ) + + self.mock_producer.flush.assert_called_once_with(10) + + def test_force_flush_timeout(self): + self.mock_producer.flush.side_effect = KafkaTimeoutError() + self.assertFalse( + OTLPLogExporter(producer=self.mock_producer).force_flush(), + ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_metric_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_metric_exporter.py new file mode 100644 index 0000000000..0d9e49ae01 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_metric_exporter.py @@ -0,0 +1,145 @@ +# 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. + + +import unittest +from logging import ERROR, WARNING +from unittest.mock import Mock, create_autospec, patch + +from kafka import KafkaProducer +from kafka.errors import InvalidRequiredAcksError, KafkaTimeoutError + +from opentelemetry.exporter.otlp.proto.kafka._internal import metric_exporter +from opentelemetry.exporter.otlp.proto.kafka.metric_exporter import ( + OTLPMetricExporter, +) +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_KAFKA_METRICS_TOPIC, +) +from opentelemetry.sdk.metrics._internal.export import MetricExportResult + + +class TestOTLPMetricExporter(unittest.TestCase): + def setUp(self): + self.mock_producer = create_autospec(KafkaProducer) + + @patch.object(metric_exporter, "encode_metrics") + def test_export(self, mock_encode_metrics): + mock_spans = Mock() + self.assertEqual( + OTLPMetricExporter(producer=self.mock_producer).export(mock_spans), + MetricExportResult.SUCCESS, + ) + + mock_encode_metrics.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "otlp_metrics", + value=mock_encode_metrics.return_value.SerializeToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once() + + @patch.dict( + "os.environ", + {OTEL_EXPORTER_KAFKA_METRICS_TOPIC: "metrics_topic"}, + ) + @patch.object(metric_exporter, "encode_metrics") + def test_export_env_metrics_topic(self, mock_encode_logs): + mock_spans = Mock() + self.assertEqual( + OTLPMetricExporter(producer=self.mock_producer).export(mock_spans), + MetricExportResult.SUCCESS, + ) + + mock_encode_logs.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "metrics_topic", + value=mock_encode_logs.return_value.SerializeToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once_with(10) + + @patch.object(metric_exporter, "encode_metrics") + def test_export_constructor_metrics_topic(self, mock_encode_logs): + mock_spans = Mock() + self.assertEqual( + OTLPMetricExporter( + producer=self.mock_producer, topic="metrics_topic" + ).export(mock_spans), + MetricExportResult.SUCCESS, + ) + + mock_encode_logs.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "metrics_topic", + value=mock_encode_logs.return_value.SerializeToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once_with(10) + + def test_shutdown(self): + OTLPMetricExporter(producer=self.mock_producer).shutdown() + + self.mock_producer.close.assert_called_once() + + @patch.object(metric_exporter, "encode_metrics") + def test_export_kafka_error(self, mock_encode_metrics): + self.mock_producer.send.side_effect = InvalidRequiredAcksError() + with self.assertLogs(level=ERROR) as error: + self.assertEqual( + OTLPMetricExporter(producer=self.mock_producer).export(Mock()), + MetricExportResult.FAILURE, + ) + self.assertIn( + "Failed to export metrics batch reason:", + error.records[0].message, + ) + + def test_export_post_shutdown_fails(self): + exporter = OTLPMetricExporter(producer=self.mock_producer) + exporter.shutdown() + + with self.assertLogs(level=WARNING) as warning: + self.assertEqual( + exporter.export(Mock()), + MetricExportResult.FAILURE, + ) + self.assertIn( + "Exporter already shutdown, ignoring batch", + warning.records[0].message, + ) + + def test_multiple_shutdown_warning(self): + exporter = OTLPMetricExporter(producer=self.mock_producer) + exporter.shutdown() + + with self.assertLogs(level=WARNING) as warning: + exporter.shutdown() + self.assertIn( + "Exporter already shutdown, ignoring call", + warning.records[0].message, + ) + + def test_force_flush(self): + self.assertTrue( + OTLPMetricExporter(producer=self.mock_producer).force_flush(), + ) + + self.mock_producer.flush.assert_called_once_with(10) + + def test_force_flush_timeout(self): + self.mock_producer.flush.side_effect = KafkaTimeoutError() + self.assertFalse( + OTLPMetricExporter(producer=self.mock_producer).force_flush(), + ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_span_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_span_exporter.py new file mode 100644 index 0000000000..f2fa00b54e --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-kafka/tests/test_kafka_span_exporter.py @@ -0,0 +1,144 @@ +# 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. + +import unittest +from logging import ERROR, WARNING +from unittest.mock import Mock, create_autospec, patch + +from kafka import KafkaProducer +from kafka.errors import InvalidRequiredAcksError, KafkaTimeoutError + +from opentelemetry.exporter.otlp.proto.kafka._internal import trace_exporter +from opentelemetry.exporter.otlp.proto.kafka.trace_exporter import ( + OTLPSpanExporter, +) +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_KAFKA_TRACES_TOPIC, +) +from opentelemetry.sdk.trace.export import SpanExportResult + + +class TestOTLPSpanExporter(unittest.TestCase): + def setUp(self): + self.mock_producer = create_autospec(KafkaProducer) + + @patch.object(trace_exporter, "encode_spans") + def test_export(self, mock_encode_spans): + mock_spans = Mock() + self.assertEqual( + OTLPSpanExporter(producer=self.mock_producer).export(mock_spans), + SpanExportResult.SUCCESS, + ) + + mock_encode_spans.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "otlp_spans", + value=mock_encode_spans.return_value.SerializePartialToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once() + + @patch.dict( + "os.environ", + {OTEL_EXPORTER_KAFKA_TRACES_TOPIC: "traces_topic"}, + ) + @patch.object(trace_exporter, "encode_spans") + def test_export_env_logs_topic(self, mock_encode_logs): + mock_spans = Mock() + self.assertEqual( + OTLPSpanExporter(producer=self.mock_producer).export(mock_spans), + SpanExportResult.SUCCESS, + ) + + mock_encode_logs.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "traces_topic", + value=mock_encode_logs.return_value.SerializePartialToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once_with(10) + + @patch.object(trace_exporter, "encode_spans") + def test_export_constructor_logs_topic(self, mock_encode_logs): + mock_spans = Mock() + self.assertEqual( + OTLPSpanExporter( + producer=self.mock_producer, topic="traces_topic" + ).export(mock_spans), + SpanExportResult.SUCCESS, + ) + + mock_encode_logs.assert_called_once_with(mock_spans) + self.mock_producer.send.assert_called_once_with( + "traces_topic", + value=mock_encode_logs.return_value.SerializePartialToString.return_value, + headers=[], + ) + self.mock_producer.send.return_value.get.assert_called_once_with(10) + + def test_shutdown(self): + OTLPSpanExporter(producer=self.mock_producer).shutdown() + + self.mock_producer.close.assert_called_once() + + @patch.object(trace_exporter, "encode_spans") + def test_export_kafka_error(self, mock_encode_spans): + self.mock_producer.send.side_effect = InvalidRequiredAcksError() + with self.assertLogs(level=ERROR) as error: + self.assertEqual( + OTLPSpanExporter(producer=self.mock_producer).export(Mock()), + SpanExportResult.FAILURE, + ) + self.assertIn( + "Failed to export span batch reason:", + error.records[0].message, + ) + + def test_export_post_shutdown_fails(self): + exporter = OTLPSpanExporter(producer=self.mock_producer) + exporter.shutdown() + + with self.assertLogs(level=WARNING) as warning: + self.assertEqual( + exporter.export(Mock()), + SpanExportResult.FAILURE, + ) + self.assertIn( + "Exporter already shutdown, ignoring batch", + warning.records[0].message, + ) + + def test_multiple_shutdown_warning(self): + exporter = OTLPSpanExporter(producer=self.mock_producer) + exporter.shutdown() + + with self.assertLogs(level=WARNING) as warning: + exporter.shutdown() + self.assertIn( + "Exporter already shutdown, ignoring call", + warning.records[0].message, + ) + + def test_force_flush(self): + self.assertTrue( + OTLPSpanExporter(producer=self.mock_producer).force_flush(), + ) + + self.mock_producer.flush.assert_called_once_with(30) + + def test_force_flush_timeout(self): + self.mock_producer.flush.side_effect = KafkaTimeoutError() + self.assertFalse( + OTLPSpanExporter(producer=self.mock_producer).force_flush(), + ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 5baf5fcd55..1cf9fda2ec 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -866,3 +866,52 @@ def channel_credential_provider() -> grpc.ChannelCredentials: This is an experimental environment variable and the name of this variable and its behavior can change in a non-backwards compatible way. """ + + +OTEL_EXPORTER_KAFKA_BROKERS = "OTEL_EXPORTER_KAFKA_BROKERS" +""" +.. envvar:: OTEL_EXPORTER_KAFKA_BROKERS + +The :envvar:`OTEL_EXPORTER_KAFKA_BROKERS` environment variable configures the brokers used +by the Kafka exporter. +Default: localhost:9092 +""" + + +OTEL_EXPORTER_KAFKA_CLIENT_ID = "OTEL_EXPORTER_KAFKA_CLIENT_ID" +""" +.. envvar:: OTEL_EXPORTER_KAFKA_CLIENT_ID + +The :envvar:`OTEL_EXPORTER_KAFKA_CLIENT_ID` environment variable configures the client id used +by the Kafka exporter. +Default: otel-exporter +""" + + +OTEL_EXPORTER_KAFKA_LOGS_TOPIC = "OTEL_EXPORTER_KAFKA_LOGS_TOPIC" +""" +.. envvar:: OTEL_EXPORTER_KAFKA_LOGS_TOPIC + +The :envvar:`OTEL_EXPORTER_KAFKA_LOGS_TOPIC` environment variable configures the topic used +by the Kafka log exporter. +Default: otlp_logs +""" + + +OTEL_EXPORTER_KAFKA_METRICS_TOPIC = "OTEL_EXPORTER_KAFKA_METRICS_TOPIC" +""" +.. envvar:: OTEL_EXPORTER_KAFKA_LOGS_TOPIC + +The :envvar:`OTEL_EXPORTER_KAFKA_LOGS_TOPIC` environment variable configures the topic used +by the Kafka log exporter. +Default: otlp_logs +""" + +OTEL_EXPORTER_KAFKA_TRACES_TOPIC = "OTEL_EXPORTER_KAFKA_TRACES_TOPIC" +""" +.. envvar:: OTEL_EXPORTER_KAFKA_TRACES_TOPIC + +The :envvar:`OTEL_EXPORTER_KAFKA_TRACES_TOPIC` environment variable configures the topic used +by the Kafka trace exporter. +Default: otlp_spans +""" diff --git a/tox.ini b/tox.ini index 52e0c1612a..2bcd4c3e32 100644 --- a/tox.ini +++ b/tox.ini @@ -58,6 +58,10 @@ envlist = pypy3-test-opentelemetry-exporter-otlp-proto-http lint-opentelemetry-exporter-otlp-proto-http + py3{9,10,11,12,13,14}-test-opentelemetry-exporter-otlp-proto-kafka + pypy3-test-opentelemetry-exporter-otlp-proto-kafka + lint-opentelemetry-exporter-otlp-proto-kafka + py3{9,10,11,12,13,14}-test-opentelemetry-exporter-prometheus pypy3-test-opentelemetry-exporter-prometheus lint-opentelemetry-exporter-prometheus @@ -128,6 +132,8 @@ deps = opentelemetry-exporter-otlp-proto-http: -r {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-http/test-requirements.txt + opentelemetry-exporter-otlp-proto-kafka: -r {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-kafka/test-requirements.txt + opentracing-shim: -r {toxinidir}/shim/opentelemetry-opentracing-shim/test-requirements.txt opencensus-shim: -r {toxinidir}/shim/opentelemetry-opencensus-shim/test-requirements.txt @@ -205,6 +211,9 @@ commands = test-opentelemetry-exporter-otlp-proto-http: pytest {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-http/tests {posargs} lint-opentelemetry-exporter-otlp-proto-http: sh -c "cd exporter && pylint --prefer-stubs yes --rcfile ../.pylintrc {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-http" + test-opentelemetry-exporter-otlp-proto-kafka: pytest {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-kafka/tests {posargs} + lint-opentelemetry-exporter-otlp-proto-kafka: sh -c "cd exporter && pylint --prefer-stubs yes --rcfile ../.pylintrc {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-kafka" + test-opentelemetry-exporter-prometheus: pytest {toxinidir}/exporter/opentelemetry-exporter-prometheus/tests {posargs} lint-opentelemetry-exporter-prometheus: sh -c "cd exporter && pylint --rcfile ../.pylintrc {toxinidir}/exporter/opentelemetry-exporter-prometheus" @@ -295,6 +304,7 @@ deps = otlpexporter: -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-common otlpexporter: -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-grpc otlpexporter: -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-http + otlpexporter: -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-kafka otlpexporter: -e {toxinidir}/exporter/opentelemetry-exporter-otlp opencensus: -e {toxinidir}/exporter/opentelemetry-exporter-opencensus @@ -350,6 +360,7 @@ deps = -e {toxinidir}/exporter/opentelemetry-exporter-otlp -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-grpc -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-http + -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-kafka -e {toxinidir}/opentelemetry-proto commands = diff --git a/uv.lock b/uv.lock index 30f2debcda..2254424d11 100644 --- a/uv.lock +++ b/uv.lock @@ -15,6 +15,7 @@ members = [ "opentelemetry-exporter-otlp-proto-common", "opentelemetry-exporter-otlp-proto-grpc", "opentelemetry-exporter-otlp-proto-http", + "opentelemetry-exporter-otlp-proto-kafka", "opentelemetry-exporter-prometheus", "opentelemetry-exporter-zipkin-json", "opentelemetry-propagator-b3", @@ -367,6 +368,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, ] +[[package]] +name = "kafka-python" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/5c/d3b6d93ed625d2cb0265e6fe0f507be544f6edde577c1b118f42566389bf/kafka_python-2.3.0.tar.gz", hash = "sha256:de65b596d26b5c894f227c35c7d29a65cea8f8a1c4f0f2b4e3e2ea60d503acc8", size = 358391, upload-time = "2025-11-21T00:47:34.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/db/694fd552295ed091e7418d02b6268ee36092d4c93211136c448fe061fe32/kafka_python-2.3.0-py2.py3-none-any.whl", hash = "sha256:831ba6dff28144d0f1145c874d391f3ebb3c2c3e940cc78d74e83f0183497c98", size = 326260, upload-time = "2025-11-21T00:47:32.561Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -492,6 +502,28 @@ requires-dist = [ ] provides-extras = ["gcp-auth"] +[[package]] +name = "opentelemetry-exporter-otlp-proto-kafka" +source = { editable = "exporter/opentelemetry-exporter-otlp-proto-kafka" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "kafka-python" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, +] + +[package.metadata] +requires-dist = [ + { name = "googleapis-common-protos", specifier = "~=1.52" }, + { name = "kafka-python", specifier = "~=2.3.0" }, + { name = "opentelemetry-api", editable = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common", editable = "exporter/opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto", editable = "opentelemetry-proto" }, + { name = "opentelemetry-sdk", editable = "opentelemetry-sdk" }, +] + [[package]] name = "opentelemetry-exporter-prometheus" source = { editable = "exporter/opentelemetry-exporter-prometheus" }