From b201b4a9eab54bc53f6a4bf13ac3af42f075a106 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 13:45:16 +0700 Subject: [PATCH 01/15] Add e2e test --- .github/workflows/ci.yml | 144 ++++++++++++++++++++++++++++++ tests/e2e/__init__.py | 0 tests/e2e/test_logzio_e2e.py | 164 +++++++++++++++++++++++++++++++++++ tox.ini | 3 +- 4 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/e2e/__init__.py create mode 100644 tests/e2e/test_logzio_e2e.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6804774 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,144 @@ +name: CI + +on: + pull_request: + +permissions: + contents: read + +jobs: + # ============================================ + # Linting with flake8 + # ============================================ + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install flake8 + run: pip install flake8 + + - name: Run flake8 + run: flake8 logzio --count --show-source --statistics + + # ============================================ + # Unit Tests across multiple Python versions + # ============================================ + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-cov future requests + + - name: Run unit tests with coverage + run: | + pytest --cov-report=term-missing --cov=logzio tests/ -v --ignore=tests/e2e/ + + - name: Upload coverage report + if: matrix.python-version == '3.11' + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: .coverage + retention-days: 5 + + # ============================================ + # E2E Integration Test with Logz.io + # Sends real logs and validates via API + # ============================================ + e2e-integration: + name: E2E Integration Test + runs-on: ubuntu-latest + # Only run E2E if unit tests pass + needs: [lint, test] + # Skip E2E if secrets are not available (e.g., fork PRs) + if: | + github.event_name == 'push' || + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest requests + + - name: Install package in development mode + run: pip install -e . + + - name: Generate unique ENV_ID + id: env-id + run: echo "value=e2e-${{ github.run_id }}-${{ github.run_attempt }}" >> $GITHUB_OUTPUT + + - name: Run E2E integration test + env: + LOGZIO_TOKEN: ${{ secrets.LOGZIO_TOKEN }} + LOGZIO_LOGS_API_KEY: ${{ secrets.LOGZIO_LOGS_API_KEY }} + LOGZIO_API_URL: ${{ secrets.LOGZIO_API_URL }} + ENV_ID: ${{ steps.env-id.outputs.value }} + run: | + pytest tests/e2e/test_logzio_e2e.py -v --tb=long + + # ============================================ + # Summary job for branch protection + # ============================================ + ci-success: + name: CI Success + runs-on: ubuntu-latest + needs: [lint, test, e2e-integration] + if: always() + steps: + - name: Check all jobs passed + run: | + if [[ "${{ needs.lint.result }}" != "success" ]]; then + echo "Lint job failed" + exit 1 + fi + if [[ "${{ needs.test.result }}" != "success" ]]; then + echo "Test job failed" + exit 1 + fi + if [[ "${{ needs.e2e-integration.result }}" != "success" && "${{ needs.e2e-integration.result }}" != "skipped" ]]; then + echo "E2E integration job failed" + exit 1 + fi + echo "All CI checks passed! ✅" + diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/test_logzio_e2e.py b/tests/e2e/test_logzio_e2e.py new file mode 100644 index 0000000..99f20c7 --- /dev/null +++ b/tests/e2e/test_logzio_e2e.py @@ -0,0 +1,164 @@ +""" +E2E Integration Tests for logzio-python-handler + +Sends logs using the handler and validates they arrive via Logz.io API. + +Required Environment Variables: + LOGZIO_TOKEN: Shipping token for sending logs + LOGZIO_LOGS_API_KEY: API token for querying logs + ENV_ID: Unique identifier for this test run +""" + +import json +import logging +import os +import time +import requests +import pytest + +from logzio.handler import LogzioHandler + +# Logz.io API URL +BASE_LOGZIO_API_URL = os.getenv("LOGZIO_API_URL", "https://api.logz.io/v1") + + +def get_env_or_skip(var_name: str) -> str: + """Get environment variable or skip test if not set.""" + value = os.environ.get(var_name) + if not value: + pytest.skip(f"Environment variable {var_name} is not set") + return value + + +def format_query(query: str) -> str: + """Format query for Logz.io search API.""" + return json.dumps({ + "query": { + "query_string": { + "query": query + } + }, + "from": 0, + "size": 100, + "sort": [{"@timestamp": {"order": "desc"}}] + }) + + +def fetch_logs(api_key: str, query: str): + """Fetch logs from Logz.io API.""" + url = f"{BASE_LOGZIO_API_URL}/search" + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "X-API-TOKEN": api_key + } + + print(f"Sending API request to {url}") + print(f"Query: {query}") + + response = requests.post( + url, + headers=headers, + data=format_query(query), + timeout=30 + ) + + if response.status_code != 200: + raise Exception(f"Unexpected status code: {response.status_code}, body: {response.text}") + + return response.json() + + +def send_test_log(token: str, env_id: str, message: str): + """Send a test log using the logzio handler.""" + handler = LogzioHandler( + token=token, + logzio_type=env_id, + logs_drain_timeout=2, + debug=True, + backup_logs=False + ) + + logger = logging.getLogger(f"e2e-test-{env_id}") + logger.setLevel(logging.INFO) + logger.handlers = [] + logger.addHandler(handler) + + logger.info(message, extra={"env_id": env_id, "test_source": "python-handler-e2e"}) + + # Flush to ensure log is sent + handler.flush() + time.sleep(3) # Wait for log to be processed + + +class TestLogzioLogs: + """E2E tests for logzio-python-handler.""" + + @pytest.fixture(autouse=True) + def setup(self): + """Set up test fixtures.""" + self.token = get_env_or_skip("LOGZIO_TOKEN") + self.api_key = get_env_or_skip("LOGZIO_LOGS_API_KEY") + self.env_id = get_env_or_skip("ENV_ID") + + def test_logs_received(self): + """Test that logs are received and have required fields.""" + # Send a test log + test_message = f"E2E test message from python handler - {self.env_id}" + send_test_log(self.token, self.env_id, test_message) + + # Wait for ingestion + print("Waiting for log ingestion...") + time.sleep(30) + + # Query logs + query = f"env_id:{self.env_id} AND type:{self.env_id}" + response = fetch_logs(self.api_key, query) + + total = response.get("hits", {}).get("total", 0) + if total == 0: + pytest.fail("No logs found") + + hits = response.get("hits", {}).get("hits", []) + for hit in hits: + source = hit.get("_source", {}) + + # Verify required fields are present + required_fields = ["message", "logger", "log_level", "type", "@timestamp"] + missing_fields = [f for f in required_fields if not source.get(f)] + + if missing_fields: + print(f"Log missing fields: {missing_fields}") + print(f"Log content: {json.dumps(source, indent=2)}") + pytest.fail(f"Missing required log fields: {missing_fields}") + + print(f"✅ Found {total} logs with all required fields") + + def test_log_content_matches(self): + """Test that log content matches what was sent.""" + test_message = f"Content validation test - {self.env_id}" + send_test_log(self.token, self.env_id, test_message) + + # Wait for ingestion + print("Waiting for log ingestion...") + time.sleep(30) + + # Query for specific message + query = f"env_id:{self.env_id} AND message:*Content*validation*" + response = fetch_logs(self.api_key, query) + + total = response.get("hits", {}).get("total", 0) + if total == 0: + pytest.fail("Test log not found") + + # Verify the message content + hit = response["hits"]["hits"][0]["_source"] + assert "Content validation test" in hit.get("message", ""), "Message content mismatch" + assert hit.get("env_id") == self.env_id, "env_id mismatch" + assert hit.get("test_source") == "python-handler-e2e", "test_source mismatch" + + print("✅ Log content matches expected values") + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tox.ini b/tox.ini index bf349a1..a94d327 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,8 @@ deps = pytest pytest-cov passenv = CI,TRAVIS,TRAVIS_* -commands = pytest --cov-report term-missing --cov logzio tests -v +# Exclude E2E tests from regular test runs (they require secrets) +commands = pytest --cov-report term-missing --cov logzio tests -v --ignore=tests/e2e/ [testenv:flake8] basepython = python3.9 From 12044d288211e18a5341aa5d80457b8fc7ecdc18 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 13:49:02 +0700 Subject: [PATCH 02/15] Update ci.yml --- .github/workflows/ci.yml | 45 +++++----------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6804774..a2513cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,30 +7,6 @@ permissions: contents: read jobs: - # ============================================ - # Linting with flake8 - # ============================================ - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install flake8 - run: pip install flake8 - - - name: Run flake8 - run: flake8 logzio --count --show-source --statistics - - # ============================================ - # Unit Tests across multiple Python versions - # ============================================ test: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest @@ -57,6 +33,9 @@ jobs: pip install -r requirements.txt pip install pytest pytest-cov future requests + - name: Install package in development mode + run: pip install -e . + - name: Run unit tests with coverage run: | pytest --cov-report=term-missing --cov=logzio tests/ -v --ignore=tests/e2e/ @@ -69,16 +48,10 @@ jobs: path: .coverage retention-days: 5 - # ============================================ - # E2E Integration Test with Logz.io - # Sends real logs and validates via API - # ============================================ e2e-integration: name: E2E Integration Test runs-on: ubuntu-latest - # Only run E2E if unit tests pass - needs: [lint, test] - # Skip E2E if secrets are not available (e.g., fork PRs) + needs: [test] if: | github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) @@ -117,21 +90,14 @@ jobs: run: | pytest tests/e2e/test_logzio_e2e.py -v --tb=long - # ============================================ - # Summary job for branch protection - # ============================================ ci-success: name: CI Success runs-on: ubuntu-latest - needs: [lint, test, e2e-integration] + needs: [test, e2e-integration] if: always() steps: - name: Check all jobs passed run: | - if [[ "${{ needs.lint.result }}" != "success" ]]; then - echo "Lint job failed" - exit 1 - fi if [[ "${{ needs.test.result }}" != "success" ]]; then echo "Test job failed" exit 1 @@ -141,4 +107,3 @@ jobs: exit 1 fi echo "All CI checks passed! ✅" - From 92da998fe2c72247d99605f421047ffdf3ff5f06 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 13:56:53 +0700 Subject: [PATCH 03/15] _remove_version_specific_fields unit tests --- tests/test_logzioHandler.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_logzioHandler.py b/tests/test_logzioHandler.py index 516b764..fc445be 100644 --- a/tests/test_logzioHandler.py +++ b/tests/test_logzioHandler.py @@ -11,6 +11,11 @@ class TestLogzioHandler(TestCase): def setUp(self): self.handler = LogzioHandler('moo') + def _remove_version_specific_fields(self, message): + """Remove fields that vary by Python version (e.g., taskName added in 3.12).""" + message.pop('taskName', None) + return message + def test_json(self): formatter = logging.Formatter( '{ "appname":"%(name)s", "functionName":"%(funcName)s", \"lineNo":"%(lineno)d", "severity":"%(' @@ -30,6 +35,7 @@ def test_json(self): formatted_message = self.handler.format_message(record) formatted_message["@timestamp"] = None + self._remove_version_specific_fields(formatted_message) self.assertDictEqual( formatted_message, @@ -44,7 +50,6 @@ def test_json(self): 'message': 'this is a test: moo.', 'path_name': 'handler_test.py', 'severity': 'NOTSET', - 'taskName': None, 'type': 'python' } ) @@ -63,6 +68,7 @@ def test_string(self): formatted_message = self.handler.format_message(record) formatted_message["@timestamp"] = None + self._remove_version_specific_fields(formatted_message) self.assertDictEqual( formatted_message, @@ -73,7 +79,6 @@ def test_string(self): 'logger': 'my-logger', 'message': 'this is a test: moo.', 'path_name': 'handler_test.py', - 'taskName': None, 'type': 'python' } ) @@ -94,6 +99,7 @@ def test_extra_formatting(self): record.__dict__["module"] = "testing" formatted_message = self.handler.format_message(record) formatted_message["@timestamp"] = None + self._remove_version_specific_fields(formatted_message) self.assertDictEqual( formatted_message, @@ -104,7 +110,6 @@ def test_extra_formatting(self): 'logger': 'my-logger', 'message': 'this is a test: moo.', 'path_name': 'handler_test.py', - 'taskName': None, 'type': 'python', 'extra_key': 'extra_value' } @@ -124,6 +129,7 @@ def test_format_string_message(self): formatted_message = self.handler.format_message(record) formatted_message["@timestamp"] = None + self._remove_version_specific_fields(formatted_message) self.assertDictEqual( formatted_message, @@ -134,7 +140,6 @@ def test_format_string_message(self): 'logger': 'my-logger', 'message': 'this is a test: moo.', 'path_name': 'handler_test.py', - 'taskName': None, 'type': 'python' } ) @@ -161,6 +166,7 @@ def test_exception(self): formatted_message = self.handler.format_message(record) formatted_message["@timestamp"] = None + self._remove_version_specific_fields(formatted_message) formatted_message["exception"] = formatted_message["exception"].replace(os.path.abspath(__file__), "") formatted_message["exception"] = re.sub(r", line \d+", "", formatted_message["exception"]) @@ -175,7 +181,6 @@ def test_exception(self): 'message': 'exception test:', 'exception': 'Traceback (most recent call last):\n\n File "", in test_exception\n raise ValueError("oops.")\n\nValueError: oops.\n', 'path_name': 'handler_test.py', - 'taskName': None, 'type': 'python', 'tags': ['staging', 'experimental'] }, From aa220e0638a209b38c789ee754e86e7ad839326a Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 14:11:41 +0700 Subject: [PATCH 04/15] secrets names --- .github/workflows/ci.yml | 7 ++----- tests/e2e/test_logzio_e2e.py | 17 ++++------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2513cd..595071a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,9 +52,6 @@ jobs: name: E2E Integration Test runs-on: ubuntu-latest needs: [test] - if: | - github.event_name == 'push' || - (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) steps: - name: Checkout code @@ -84,7 +81,7 @@ jobs: - name: Run E2E integration test env: LOGZIO_TOKEN: ${{ secrets.LOGZIO_TOKEN }} - LOGZIO_LOGS_API_KEY: ${{ secrets.LOGZIO_LOGS_API_KEY }} + LOGZIO_API_KEY: ${{ secrets.LOGZIO_API_KEY }} LOGZIO_API_URL: ${{ secrets.LOGZIO_API_URL }} ENV_ID: ${{ steps.env-id.outputs.value }} run: | @@ -102,7 +99,7 @@ jobs: echo "Test job failed" exit 1 fi - if [[ "${{ needs.e2e-integration.result }}" != "success" && "${{ needs.e2e-integration.result }}" != "skipped" ]]; then + if [[ "${{ needs.e2e-integration.result }}" != "success" ]]; then echo "E2E integration job failed" exit 1 fi diff --git a/tests/e2e/test_logzio_e2e.py b/tests/e2e/test_logzio_e2e.py index 99f20c7..64aef6d 100644 --- a/tests/e2e/test_logzio_e2e.py +++ b/tests/e2e/test_logzio_e2e.py @@ -5,7 +5,7 @@ Required Environment Variables: LOGZIO_TOKEN: Shipping token for sending logs - LOGZIO_LOGS_API_KEY: API token for querying logs + LOGZIO_API_KEY: API token for querying logs ENV_ID: Unique identifier for this test run """ @@ -18,7 +18,6 @@ from logzio.handler import LogzioHandler -# Logz.io API URL BASE_LOGZIO_API_URL = os.getenv("LOGZIO_API_URL", "https://api.logz.io/v1") @@ -86,9 +85,8 @@ def send_test_log(token: str, env_id: str, message: str): logger.info(message, extra={"env_id": env_id, "test_source": "python-handler-e2e"}) - # Flush to ensure log is sent handler.flush() - time.sleep(3) # Wait for log to be processed + time.sleep(3) class TestLogzioLogs: @@ -98,20 +96,17 @@ class TestLogzioLogs: def setup(self): """Set up test fixtures.""" self.token = get_env_or_skip("LOGZIO_TOKEN") - self.api_key = get_env_or_skip("LOGZIO_LOGS_API_KEY") + self.api_key = get_env_or_skip("LOGZIO_API_KEY") self.env_id = get_env_or_skip("ENV_ID") def test_logs_received(self): """Test that logs are received and have required fields.""" - # Send a test log test_message = f"E2E test message from python handler - {self.env_id}" send_test_log(self.token, self.env_id, test_message) - # Wait for ingestion print("Waiting for log ingestion...") - time.sleep(30) + time.sleep(180) - # Query logs query = f"env_id:{self.env_id} AND type:{self.env_id}" response = fetch_logs(self.api_key, query) @@ -123,7 +118,6 @@ def test_logs_received(self): for hit in hits: source = hit.get("_source", {}) - # Verify required fields are present required_fields = ["message", "logger", "log_level", "type", "@timestamp"] missing_fields = [f for f in required_fields if not source.get(f)] @@ -139,11 +133,9 @@ def test_log_content_matches(self): test_message = f"Content validation test - {self.env_id}" send_test_log(self.token, self.env_id, test_message) - # Wait for ingestion print("Waiting for log ingestion...") time.sleep(30) - # Query for specific message query = f"env_id:{self.env_id} AND message:*Content*validation*" response = fetch_logs(self.api_key, query) @@ -151,7 +143,6 @@ def test_log_content_matches(self): if total == 0: pytest.fail("Test log not found") - # Verify the message content hit = response["hits"]["hits"][0]["_source"] assert "Content validation test" in hit.get("message", ""), "Message content mismatch" assert hit.get("env_id") == self.env_id, "env_id mismatch" From 393bc4b631690aa123cdbbb9eaa77a9f1b7b2c54 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 14:24:26 +0700 Subject: [PATCH 05/15] more python versions --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 595071a..c53a7b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Checkout code @@ -82,7 +82,6 @@ jobs: env: LOGZIO_TOKEN: ${{ secrets.LOGZIO_TOKEN }} LOGZIO_API_KEY: ${{ secrets.LOGZIO_API_KEY }} - LOGZIO_API_URL: ${{ secrets.LOGZIO_API_URL }} ENV_ID: ${{ steps.env-id.outputs.value }} run: | pytest tests/e2e/test_logzio_e2e.py -v --tb=long From 4898f0a5431640584a9f5b111a218c30142cb5c5 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 14:27:57 +0700 Subject: [PATCH 06/15] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7687fd4..a53f129 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ pip install logzio-python-handler[opentelemetry-logging] ## Tested Python Versions -Travis CI will build this handler and test against: +CI will build this handler and test against: - "3.5" - "3.6" @@ -49,6 +49,9 @@ Travis CI will build this handler and test against: - "3.9" - "3.10" - "3.11" +- "3.12" +- "3.13" +- "3.14" We can't ensure compatibility to any other version, as we can't test it automatically. From 1e321998b6a10f2e6f4fbaa62e8a9c2161f3fc06 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 14:39:59 +0700 Subject: [PATCH 07/15] Update test_logzio_e2e.py --- tests/e2e/test_logzio_e2e.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/test_logzio_e2e.py b/tests/e2e/test_logzio_e2e.py index 64aef6d..33b926f 100644 --- a/tests/e2e/test_logzio_e2e.py +++ b/tests/e2e/test_logzio_e2e.py @@ -134,7 +134,7 @@ def test_log_content_matches(self): send_test_log(self.token, self.env_id, test_message) print("Waiting for log ingestion...") - time.sleep(30) + time.sleep(180) query = f"env_id:{self.env_id} AND message:*Content*validation*" response = fetch_logs(self.api_key, query) From 2cf0f288cce2b02fcc9d9eb38552f4750b58e0c4 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 14:54:37 +0700 Subject: [PATCH 08/15] Update test_logzio_e2e.py --- tests/e2e/test_logzio_e2e.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/test_logzio_e2e.py b/tests/e2e/test_logzio_e2e.py index 33b926f..0577c67 100644 --- a/tests/e2e/test_logzio_e2e.py +++ b/tests/e2e/test_logzio_e2e.py @@ -105,7 +105,7 @@ def test_logs_received(self): send_test_log(self.token, self.env_id, test_message) print("Waiting for log ingestion...") - time.sleep(180) + time.sleep(240) query = f"env_id:{self.env_id} AND type:{self.env_id}" response = fetch_logs(self.api_key, query) @@ -134,7 +134,7 @@ def test_log_content_matches(self): send_test_log(self.token, self.env_id, test_message) print("Waiting for log ingestion...") - time.sleep(180) + time.sleep(240) query = f"env_id:{self.env_id} AND message:*Content*validation*" response = fetch_logs(self.api_key, query) From 7b7d080df87e1c64bfd378ce5dbce89c68a48ab8 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 15:52:26 +0700 Subject: [PATCH 09/15] Update test_logzio_e2e.py --- tests/e2e/test_logzio_e2e.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/e2e/test_logzio_e2e.py b/tests/e2e/test_logzio_e2e.py index 0577c67..27f0c8e 100644 --- a/tests/e2e/test_logzio_e2e.py +++ b/tests/e2e/test_logzio_e2e.py @@ -12,6 +12,7 @@ import json import logging import os +import re import time import requests import pytest @@ -136,17 +137,28 @@ def test_log_content_matches(self): print("Waiting for log ingestion...") time.sleep(240) - query = f"env_id:{self.env_id} AND message:*Content*validation*" + query = f"env_id:{self.env_id}" response = fetch_logs(self.api_key, query) - total = response.get("hits", {}).get("total", 0) - if total == 0: - pytest.fail("Test log not found") + hits = response.get("hits", {}).get("hits", []) + if not hits: + pytest.fail("No logs found for env_id") + + # Find the log with matching message using regex + pattern = re.compile(r"Content\s+validation\s+test") + matching_log = None + for hit in hits: + source = hit.get("_source", {}) + message = source.get("message", "") + if pattern.search(message): + matching_log = source + break + + if not matching_log: + pytest.fail("Test log with 'Content validation test' not found in message field") - hit = response["hits"]["hits"][0]["_source"] - assert "Content validation test" in hit.get("message", ""), "Message content mismatch" - assert hit.get("env_id") == self.env_id, "env_id mismatch" - assert hit.get("test_source") == "python-handler-e2e", "test_source mismatch" + assert matching_log.get("env_id") == self.env_id, "env_id mismatch" + assert matching_log.get("test_source") == "python-handler-e2e", "test_source mismatch" print("✅ Log content matches expected values") From 19b82f0a3fdf0f4fae557315d169f82288a013f3 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 15:52:48 +0700 Subject: [PATCH 10/15] Update test_logzio_e2e.py --- tests/e2e/test_logzio_e2e.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/test_logzio_e2e.py b/tests/e2e/test_logzio_e2e.py index 27f0c8e..49693c7 100644 --- a/tests/e2e/test_logzio_e2e.py +++ b/tests/e2e/test_logzio_e2e.py @@ -106,7 +106,7 @@ def test_logs_received(self): send_test_log(self.token, self.env_id, test_message) print("Waiting for log ingestion...") - time.sleep(240) + time.sleep(180) query = f"env_id:{self.env_id} AND type:{self.env_id}" response = fetch_logs(self.api_key, query) @@ -135,7 +135,7 @@ def test_log_content_matches(self): send_test_log(self.token, self.env_id, test_message) print("Waiting for log ingestion...") - time.sleep(240) + time.sleep(180) query = f"env_id:{self.env_id}" response = fetch_logs(self.api_key, query) From c6a6eabeabe9907d2f24cff2a1e089f3367acbf2 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 16:02:00 +0700 Subject: [PATCH 11/15] Update dependabot.yaml --- .github/dependabot.yaml | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index ed14700..44e5a21 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -1,17 +1,40 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# Dependabot configuration for automated dependency updates +# See: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - - package-ecosystem: "pip" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: "pip" + directory: "/" schedule: interval: "weekly" + day: "monday" + time: "09:00" + timezone: "Asia/Jerusalem" + groups: + python-dependencies: + patterns: + - "*" + commit-message: + prefix: "deps" + labels: + - "dependencies" + - "python" + open-pull-requests-limit: 5 + - package-ecosystem: "github-actions" - # Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.) directory: "/" schedule: - # Check for updates to GitHub Actions every weekday interval: "weekly" + day: "monday" + time: "09:00" + timezone: "Asia/Jerusalem" + groups: + github-actions: + patterns: + - "*" + commit-message: + prefix: "ci" + labels: + - "dependencies" + - "github-actions" + open-pull-requests-limit: 5 From f06debbc51cd22c0963b75b4369ad5baadd662a5 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 18:53:17 +0700 Subject: [PATCH 12/15] Create dependabot-automerge.yml --- .github/workflows/dependabot-automerge.yml | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/dependabot-automerge.yml diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 0000000..59a7041 --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,52 @@ +name: Dependabot Auto-Merge + +# This workflow runs AFTER CI completes for Dependabot PRs. +# It waits for CI to pass, then enables auto-merge. + +on: + workflow_run: + workflows: ["CI"] + types: + - completed + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + name: Auto-Merge Dependabot PR + runs-on: ubuntu-latest + # Run if: + # 1. CI passed + # 2. It was triggered by Dependabot + # 3. It was a pull request + if: | + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.actor.login == 'dependabot[bot]' && + github.event.workflow_run.event == 'pull_request' + + steps: + - name: Checkout for metadata + uses: actions/checkout@v4 + + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Auto-merge patch/minor updates + if: | + steps.metadata.outputs.update-type == 'version-update:semver-patch' || + steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: | + echo "Enabling auto-merge for ${{ steps.metadata.outputs.dependency-names }} (${{ steps.metadata.outputs.update-type }})" + gh pr merge --auto --merge "${{ github.event.workflow_run.head_branch }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Skip major updates + if: steps.metadata.outputs.update-type == 'version-update:semver-major' + run: | + echo "Major version update for ${{ steps.metadata.outputs.dependency-names }} - review required" From e8985986fe48152c02875c581978de544b0f4fe0 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 18:53:25 +0700 Subject: [PATCH 13/15] Create dependabot-notifications.yml --- .../workflows/dependabot-notifications.yml | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 .github/workflows/dependabot-notifications.yml diff --git a/.github/workflows/dependabot-notifications.yml b/.github/workflows/dependabot-notifications.yml new file mode 100644 index 0000000..3f9b7c6 --- /dev/null +++ b/.github/workflows/dependabot-notifications.yml @@ -0,0 +1,142 @@ +name: Dependabot Notifications + +# This workflow sends Slack notifications when CI completes for Dependabot PRs. +# It uses workflow_run to trigger AFTER the CI workflow finishes. + + +on: + workflow_run: + workflows: ["CI"] + types: + - completed + +permissions: + contents: read + pull-requests: read + actions: read + +jobs: + notify-success: + name: Notify Success + runs-on: ubuntu-latest + if: | + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.actor.login == 'dependabot[bot]' + + steps: + - name: Get PR information + id: pr-info + uses: actions/github-script@v7 + with: + script: | + const { data: pullRequests } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}` + }); + + if (pullRequests.length > 0) { + const pr = pullRequests[0]; + core.setOutput('pr_number', pr.number); + core.setOutput('pr_title', pr.title); + core.setOutput('pr_url', pr.html_url); + core.setOutput('found', 'true'); + } else { + core.setOutput('found', 'false'); + } + + - name: Send Slack notification (Success) + if: steps.pr-info.outputs.found == 'true' + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_COLOR: "#36a64f" + SLACK_USERNAME: "Dependabot" + SLACK_ICON: "https://avatars.githubusercontent.com/in/29110?s=64&v=4" + SLACK_TITLE: "✅ Dependabot Update - CI Passed" + SLACK_MESSAGE: | + *Repository:* ${{ github.repository }} + *PR:* <${{ steps.pr-info.outputs.pr_url }}|#${{ steps.pr-info.outputs.pr_number }}> ${{ steps.pr-info.outputs.pr_title }} + *Status:* All CI checks passed! Auto-merge has been enabled. + + The PR will be automatically merged once all branch protection requirements are satisfied. + SLACK_FOOTER: "logzio-python-handler CI" + + + notify-failure: + name: Notify Failure + runs-on: ubuntu-latest + if: | + github.event.workflow_run.conclusion == 'failure' && + github.event.workflow_run.actor.login == 'dependabot[bot]' + + steps: + - name: Get PR information + id: pr-info + uses: actions/github-script@v7 + with: + script: | + const { data: pullRequests } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}` + }); + + if (pullRequests.length > 0) { + const pr = pullRequests[0]; + core.setOutput('pr_number', pr.number); + core.setOutput('pr_title', pr.title); + core.setOutput('pr_url', pr.html_url); + core.setOutput('found', 'true'); + } else { + core.setOutput('found', 'false'); + } + + - name: Get workflow run URL + id: run-url + run: | + echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" >> $GITHUB_OUTPUT + + - name: Send Slack notification (Failure) + if: steps.pr-info.outputs.found == 'true' + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_COLOR: "#ff0000" + SLACK_USERNAME: "Dependabot" + SLACK_ICON: "https://avatars.githubusercontent.com/in/29110?s=64&v=4" + SLACK_TITLE: "🚨 Dependabot Update - CI Failed" + SLACK_MESSAGE: | + *Repository:* ${{ github.repository }} + *PR:* <${{ steps.pr-info.outputs.pr_url }}|#${{ steps.pr-info.outputs.pr_number }}> ${{ steps.pr-info.outputs.pr_title }} + *Status:* CI checks failed - manual intervention required! + + <${{ steps.run-url.outputs.url }}|View CI Run> to investigate the failure. + SLACK_FOOTER: "logzio-python-handler CI" + + notify-merged: + name: Notify Merged + runs-on: ubuntu-latest + if: | + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + contains(github.event.workflow_run.head_commit.message, 'dependabot') + + steps: + - name: Send Slack notification (Merged) + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_COLOR: "#2eb886" + SLACK_USERNAME: "Dependabot" + SLACK_ICON: "https://avatars.githubusercontent.com/in/29110?s=64&v=4" + SLACK_TITLE: "🎉 Dependencies Updated Successfully" + SLACK_MESSAGE: | + *Repository:* ${{ github.repository }} + *Commit:* <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }}|${{ github.event.workflow_run.head_sha }}> + + Dependabot updates have been merged and CI passed on main branch. + SLACK_FOOTER: "logzio-python-handler CI" + From 2df2ec519dd6dde07bc43e5aec83e3a306de8f34 Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 18:56:37 +0700 Subject: [PATCH 14/15] Update dependabot-automerge.yml --- .github/workflows/dependabot-automerge.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 59a7041..c5d8d51 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -42,9 +42,10 @@ jobs: steps.metadata.outputs.update-type == 'version-update:semver-minor' run: | echo "Enabling auto-merge for ${{ steps.metadata.outputs.dependency-names }} (${{ steps.metadata.outputs.update-type }})" - gh pr merge --auto --merge "${{ github.event.workflow_run.head_branch }}" + gh pr merge --auto --merge "$HEAD_BRANCH" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} - name: Skip major updates if: steps.metadata.outputs.update-type == 'version-update:semver-major' From 16bccf03dbaa789eb4978f74ab7d0379ca3b3c9d Mon Sep 17 00:00:00 2001 From: Yotam loewenbach Date: Wed, 31 Dec 2025 19:02:36 +0700 Subject: [PATCH 15/15] use crl and not rtCamp/action-slack-notify@v2 --- .../workflows/dependabot-notifications.yml | 112 ++++++++++++------ 1 file changed, 77 insertions(+), 35 deletions(-) diff --git a/.github/workflows/dependabot-notifications.yml b/.github/workflows/dependabot-notifications.yml index 3f9b7c6..768d56e 100644 --- a/.github/workflows/dependabot-notifications.yml +++ b/.github/workflows/dependabot-notifications.yml @@ -48,20 +48,35 @@ jobs: - name: Send Slack notification (Success) if: steps.pr-info.outputs.found == 'true' - uses: rtCamp/action-slack-notify@v2 + run: | + curl -X POST -H 'Content-type: application/json' --data '{ + "attachments": [{ + "color": "#36a64f", + "blocks": [ + { + "type": "header", + "text": {"type": "plain_text", "text": "✅ Dependabot Update - CI Passed", "emoji": true} + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": "*Repository:*\n${{ github.repository }}"}, + {"type": "mrkdwn", "text": "*PR:*\n<${{ steps.pr-info.outputs.pr_url }}|#${{ steps.pr-info.outputs.pr_number }}>"} + ] + }, + { + "type": "section", + "text": {"type": "mrkdwn", "text": "${{ steps.pr-info.outputs.pr_title }}"} + }, + { + "type": "context", + "elements": [{"type": "mrkdwn", "text": "Auto-merge enabled • logzio-python-handler CI"}] + } + ] + }] + }' "$SLACK_WEBHOOK" env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - SLACK_COLOR: "#36a64f" - SLACK_USERNAME: "Dependabot" - SLACK_ICON: "https://avatars.githubusercontent.com/in/29110?s=64&v=4" - SLACK_TITLE: "✅ Dependabot Update - CI Passed" - SLACK_MESSAGE: | - *Repository:* ${{ github.repository }} - *PR:* <${{ steps.pr-info.outputs.pr_url }}|#${{ steps.pr-info.outputs.pr_number }}> ${{ steps.pr-info.outputs.pr_title }} - *Status:* All CI checks passed! Auto-merge has been enabled. - - The PR will be automatically merged once all branch protection requirements are satisfied. - SLACK_FOOTER: "logzio-python-handler CI" notify-failure: @@ -101,20 +116,35 @@ jobs: - name: Send Slack notification (Failure) if: steps.pr-info.outputs.found == 'true' - uses: rtCamp/action-slack-notify@v2 + run: | + curl -X POST -H 'Content-type: application/json' --data '{ + "attachments": [{ + "color": "#ff0000", + "blocks": [ + { + "type": "header", + "text": {"type": "plain_text", "text": "🚨 Dependabot Update - CI Failed", "emoji": true} + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": "*Repository:*\n${{ github.repository }}"}, + {"type": "mrkdwn", "text": "*PR:*\n<${{ steps.pr-info.outputs.pr_url }}|#${{ steps.pr-info.outputs.pr_number }}>"} + ] + }, + { + "type": "section", + "text": {"type": "mrkdwn", "text": "${{ steps.pr-info.outputs.pr_title }}\n\n<${{ steps.run-url.outputs.url }}|View CI Run> to investigate"} + }, + { + "type": "context", + "elements": [{"type": "mrkdwn", "text": "Manual intervention required • logzio-python-handler CI"}] + } + ] + }] + }' "$SLACK_WEBHOOK" env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - SLACK_COLOR: "#ff0000" - SLACK_USERNAME: "Dependabot" - SLACK_ICON: "https://avatars.githubusercontent.com/in/29110?s=64&v=4" - SLACK_TITLE: "🚨 Dependabot Update - CI Failed" - SLACK_MESSAGE: | - *Repository:* ${{ github.repository }} - *PR:* <${{ steps.pr-info.outputs.pr_url }}|#${{ steps.pr-info.outputs.pr_number }}> ${{ steps.pr-info.outputs.pr_title }} - *Status:* CI checks failed - manual intervention required! - - <${{ steps.run-url.outputs.url }}|View CI Run> to investigate the failure. - SLACK_FOOTER: "logzio-python-handler CI" notify-merged: name: Notify Merged @@ -126,17 +156,29 @@ jobs: steps: - name: Send Slack notification (Merged) - uses: rtCamp/action-slack-notify@v2 + run: | + curl -X POST -H 'Content-type: application/json' --data '{ + "attachments": [{ + "color": "#2eb886", + "blocks": [ + { + "type": "header", + "text": {"type": "plain_text", "text": "🎉 Dependencies Updated Successfully", "emoji": true} + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": "*Repository:*\n${{ github.repository }}"}, + {"type": "mrkdwn", "text": "*Commit:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }}|${{ github.event.workflow_run.head_sha }}>"} + ] + }, + { + "type": "context", + "elements": [{"type": "mrkdwn", "text": "Merged to main • logzio-python-handler CI"}] + } + ] + }] + }' "$SLACK_WEBHOOK" env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - SLACK_COLOR: "#2eb886" - SLACK_USERNAME: "Dependabot" - SLACK_ICON: "https://avatars.githubusercontent.com/in/29110?s=64&v=4" - SLACK_TITLE: "🎉 Dependencies Updated Successfully" - SLACK_MESSAGE: | - *Repository:* ${{ github.repository }} - *Commit:* <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }}|${{ github.event.workflow_run.head_sha }}> - - Dependabot updates have been merged and CI passed on main branch. - SLACK_FOOTER: "logzio-python-handler CI"