From ab0b7e14c810159e6b216184a69cde40cd64c80b Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Fri, 21 Jul 2023 19:29:34 +0800 Subject: [PATCH] feat: send events to Google Analytics 4 in python version --- httprunner/cli.py | 4 +- httprunner/make.py | 4 +- httprunner/runner.py | 3 +- httprunner/utils.py | 94 +++++++++++++++++++++++++++++++++++----- httprunner/utils_test.py | 11 ++++- 5 files changed, 99 insertions(+), 17 deletions(-) diff --git a/httprunner/cli.py b/httprunner/cli.py index 49a6f5f4..427ee4bb 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -9,7 +9,7 @@ from loguru import logger from httprunner import __description__, __version__ from httprunner.compat import ensure_cli_args from httprunner.make import init_make_parser, main_make -from httprunner.utils import ga_client, init_logger, init_sentry_sdk +from httprunner.utils import ga4_client, init_logger, init_sentry_sdk init_sentry_sdk() @@ -22,7 +22,7 @@ def init_parser_run(subparsers): def main_run(extra_args) -> enum.IntEnum: - ga_client.track_event("RunAPITests", "hrun") + ga4_client.send_event("hrun") # keep compatibility with v2 extra_args = ensure_cli_args(extra_args) diff --git a/httprunner/make.py b/httprunner/make.py index 24d4b508..d2b38ed8 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -22,7 +22,7 @@ from httprunner.loader import ( load_testcase, ) from httprunner.response import uniform_validator -from httprunner.utils import ga_client, is_support_multiprocessing +from httprunner.utils import ga4_client, is_support_multiprocessing """ cache converted pytest files, avoid duplicate making """ @@ -541,7 +541,7 @@ def main_make(tests_paths: List[Text]) -> List[Text]: if not tests_paths: return [] - ga_client.track_event("ConvertTests", "hmake") + ga4_client.send_event("hmake") for tests_path in tests_paths: tests_path = ensure_path_sep(tests_path) diff --git a/httprunner/runner.py b/httprunner/runner.py index 5aa6ce1a..764c4fcd 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -27,7 +27,7 @@ from httprunner.models import ( VariablesMapping, ) from httprunner.parser import Parser -from httprunner.utils import LOGGER_FORMAT, merge_variables +from httprunner.utils import LOGGER_FORMAT, merge_variables, ga4_client class SessionRunner(object): @@ -210,6 +210,7 @@ class SessionRunner(object): def test_start(self, param: Dict = None) -> "SessionRunner": """main entrance, discovered by pytest""" + ga4_client.send_event("test_start") print("\n") self.__init() self.__parse_config(param) diff --git a/httprunner/utils.py b/httprunner/utils.py index cbcc8f8e..303c1e6b 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -5,7 +5,9 @@ import json import os import os.path import platform +import random import sys +import time import uuid from multiprocessing import Queue from typing import Any, Dict, List, Text @@ -18,6 +20,16 @@ from httprunner import __version__, exceptions from httprunner.models import VariablesMapping +def get_platform(): + return { + "httprunner_version": __version__, + "python_version": "{} {}".format( + platform.python_implementation(), platform.python_version() + ), + "platform": platform.platform(), + } + + def init_sentry_sdk(): if os.getenv("DISABLE_SENTRY") == "true": return @@ -30,8 +42,78 @@ def init_sentry_sdk(): scope.set_user({"id": uuid.getnode()}) -class GAClient(object): +class GA4Client(object): + def __init__( + self, measurement_id: str, api_secret: str, debug: bool = False + ) -> None: + self.http_client = requests.Session() + self.debug = debug + if debug: + uri = "https://www.google-analytics.com/debug/mp/collect" + else: + uri = "https://www.google-analytics.com/mp/collect" + + self.uri = f"{uri}?measurement_id={measurement_id}&api_secret={api_secret}" + self.user_id = str(uuid.getnode()) + self.common_event_params = get_platform() + + # do not send GA events in CI environment + self.__is_ci = os.getenv("DISABLE_GA") == "true" + + def send_event(self, name: str, event_params: dict = None) -> None: + if self.__is_ci: + return + + event_params = event_params or {} + event_params.update(self.common_event_params) + event = { + "name": name, + "params": event_params, + } + + payload = { + "client_id": f"{random.randint(-2147483648, 2147483647)}.{int(time.time())}", + "user_id": self.user_id, + "timestamp_micros": int(time.time() * 10**6), + "events": [event], + } + + if self.debug: + logger.debug(f"send GA4 event, uri: {self.uri}, payload: {payload}") + + try: + resp = self.http_client.post(self.uri, json=payload, timeout=5) + except Exception as err: # ProxyError, SSLError, ConnectionError + logger.error(f"request GA4 failed, error: {err}") + return + + if resp.status_code >= 300: + logger.error( + f"validation response got unexpected status: {resp.status_code}" + ) + return + + if not self.debug: + return + + try: + resp_body = resp.json() + logger.debug( + "get GA4 validation response, " + f"status code: {resp.status_code}, body: {resp_body}" + ) + except Exception: + pass + + +GA4_MEASUREMENT_ID = "G-9KHR3VC2LN" +GA4_API_SECRET = "w7lKNQIrQsKNS4ikgMPp0Q" + +ga4_client = GA4Client(GA4_MEASUREMENT_ID, GA4_API_SECRET, False) + + +class GAClient(object): version = "1" # GA API Version report_url = "https://www.google-analytics.com/collect" report_debug_url = ( @@ -219,16 +301,6 @@ def omit_long_data(body, omit_len=512): return omitted_body + appendix_str -def get_platform(): - return { - "httprunner_version": __version__, - "python_version": "{} {}".format( - platform.python_implementation(), platform.python_version() - ), - "platform": platform.platform(), - } - - def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List): def get_index_from_list(lst: List, item: Any): try: diff --git a/httprunner/utils_test.py b/httprunner/utils_test.py index fab35acd..10ccb479 100644 --- a/httprunner/utils_test.py +++ b/httprunner/utils_test.py @@ -7,7 +7,7 @@ from pathlib import Path import toml from httprunner import __version__, loader, utils -from httprunner.utils import ExtendJSONEncoder, merge_variables +from httprunner.utils import ExtendJSONEncoder, merge_variables, ga4_client class TestUtils(unittest.TestCase): @@ -160,3 +160,12 @@ class TestUtils(unittest.TestCase): pyproject = toml.loads(open(str(path)).read()) pyproject_version = pyproject["tool"]["poetry"]["version"] self.assertEqual(pyproject_version, __version__) + + def test_ga4_send_event(self): + ga4_client.send_event( + "httprunner_debug_event", + { + "a": 123, + "b": 456, + }, + )