Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:32:40 +08:00
commit 0ea8352871
72 changed files with 30043 additions and 0 deletions

View File

@@ -0,0 +1,370 @@
"""
GA4 Measurement Protocol - Complete Python Implementation Example
Demonstrates server-side event tracking with validation, error handling, and batching
"""
import requests
import json
import uuid
import time
from typing import Dict, List, Optional
from datetime import datetime
class GA4MeasurementProtocol:
"""GA4 Measurement Protocol client with validation and error handling"""
def __init__(self, measurement_id: str, api_secret: str, debug: bool = False):
"""
Initialize GA4 Measurement Protocol client
Args:
measurement_id: GA4 Measurement ID (G-XXXXXXXXXX)
api_secret: API secret from GA4 Data Streams
debug: Use debug endpoint for validation
"""
self.measurement_id = measurement_id
self.api_secret = api_secret
self.debug = debug
self.endpoint = self._get_endpoint()
def _get_endpoint(self) -> str:
"""Get appropriate endpoint (production or debug)"""
base = "debug/mp" if self.debug else "mp"
return f"https://www.google-analytics.com/{base}/collect?measurement_id={self.measurement_id}&api_secret={self.api_secret}"
def send_event(
self,
client_id: str,
event_name: str,
event_params: Optional[Dict] = None,
user_id: Optional[str] = None,
user_properties: Optional[Dict] = None,
timestamp_micros: Optional[int] = None,
consent: Optional[Dict] = None
) -> bool:
"""
Send single event to GA4
Args:
client_id: Unique client identifier (UUID)
event_name: Event name (lowercase, underscores, ≤40 chars)
event_params: Event parameters dictionary
user_id: Optional user ID for cross-device tracking
user_properties: Optional user properties
timestamp_micros: Optional event timestamp in microseconds
consent: Optional consent status
Returns:
bool: True if successful, False otherwise
"""
payload = self._build_payload(
client_id=client_id,
events=[{
"name": event_name,
"params": event_params or {}
}],
user_id=user_id,
user_properties=user_properties,
timestamp_micros=timestamp_micros,
consent=consent
)
return self._send_request(payload)
def send_batch(
self,
client_id: str,
events: List[Dict],
user_id: Optional[str] = None,
user_properties: Optional[Dict] = None,
consent: Optional[Dict] = None
) -> bool:
"""
Send batch of events (max 25)
Args:
client_id: Unique client identifier
events: List of event dictionaries with 'name' and 'params'
user_id: Optional user ID
user_properties: Optional user properties
consent: Optional consent status
Returns:
bool: True if successful, False otherwise
"""
if len(events) > 25:
raise ValueError("Maximum 25 events per batch")
payload = self._build_payload(
client_id=client_id,
events=events,
user_id=user_id,
user_properties=user_properties,
consent=consent
)
return self._send_request(payload)
def _build_payload(
self,
client_id: str,
events: List[Dict],
user_id: Optional[str] = None,
user_properties: Optional[Dict] = None,
timestamp_micros: Optional[int] = None,
consent: Optional[Dict] = None
) -> Dict:
"""Build request payload"""
payload = {
"client_id": client_id,
"events": events
}
if user_id:
payload["user_id"] = user_id
if user_properties:
# Format user properties
formatted_props = {
key: {"value": value}
for key, value in user_properties.items()
}
payload["user_properties"] = formatted_props
if timestamp_micros:
payload["timestamp_micros"] = str(timestamp_micros)
if consent:
payload["consent"] = consent
return payload
def _send_request(self, payload: Dict, max_retries: int = 3) -> bool:
"""
Send request with retry logic
Args:
payload: Request payload
max_retries: Maximum retry attempts
Returns:
bool: True if successful
"""
for attempt in range(max_retries):
try:
response = requests.post(
self.endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(payload),
timeout=10
)
if response.status_code == 204:
return True
if response.status_code == 200 and self.debug:
# Debug endpoint returns validation messages
validation = response.json()
if not validation.get("validationMessages"):
print("✓ Validation passed")
return True
else:
print("✗ Validation errors:")
for msg in validation["validationMessages"]:
print(f" - {msg['fieldPath']}: {msg['description']}")
return False
if response.status_code == 429:
# Rate limited - exponential backoff
wait_time = (2 ** attempt) + (time.time() % 1)
print(f"Rate limited. Waiting {wait_time:.2f}s...")
time.sleep(wait_time)
continue
print(f"Error: HTTP {response.status_code}")
return False
except requests.RequestException as e:
print(f"Request failed: {e}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
return False
# ============================================================================
# USAGE EXAMPLES
# ============================================================================
def example_page_view():
"""Example: Track page view"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret",
debug=True # Use debug endpoint for testing
)
success = ga.send_event(
client_id=str(uuid.uuid4()),
event_name="page_view",
event_params={
"page_location": "https://example.com/products",
"page_title": "Products Page",
"page_referrer": "https://google.com"
}
)
print(f"Page view sent: {success}")
def example_purchase():
"""Example: Track purchase"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
success = ga.send_event(
client_id="client_12345",
event_name="purchase",
event_params={
"transaction_id": f"T_{int(time.time())}",
"value": 99.99,
"currency": "USD",
"tax": 8.00,
"shipping": 5.00,
"items": [{
"item_id": "SKU_123",
"item_name": "Blue T-Shirt",
"price": 99.99,
"quantity": 1,
"item_category": "Apparel"
}]
}
)
print(f"Purchase sent: {success}")
def example_user_signup():
"""Example: Track user signup with user properties"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
success = ga.send_event(
client_id=str(uuid.uuid4()),
user_id="user_456",
event_name="sign_up",
event_params={
"method": "email"
},
user_properties={
"user_tier": "free",
"signup_date": datetime.now().strftime("%Y-%m-%d")
}
)
print(f"Sign up sent: {success}")
def example_batch_events():
"""Example: Send multiple events in batch"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
events = [
{
"name": "view_item",
"params": {
"currency": "USD",
"value": 29.99,
"items": [{
"item_id": "SKU_123",
"item_name": "Product",
"price": 29.99
}]
}
},
{
"name": "add_to_cart",
"params": {
"currency": "USD",
"value": 29.99,
"items": [{
"item_id": "SKU_123",
"item_name": "Product",
"price": 29.99,
"quantity": 1
}]
}
},
{
"name": "begin_checkout",
"params": {
"currency": "USD",
"value": 34.99,
"items": [{
"item_id": "SKU_123",
"item_name": "Product",
"price": 29.99,
"quantity": 1
}]
}
}
]
success = ga.send_batch(
client_id="client_789",
events=events
)
print(f"Batch sent: {success}")
def example_with_consent():
"""Example: Send event with consent parameters"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
success = ga.send_event(
client_id=str(uuid.uuid4()),
event_name="page_view",
event_params={
"page_location": "https://example.com"
},
consent={
"ad_storage": "denied",
"analytics_storage": "granted",
"ad_user_data": "denied",
"ad_personalization": "denied"
}
)
print(f"Event with consent sent: {success}")
if __name__ == "__main__":
# Run examples
print("=== GA4 Measurement Protocol Examples ===\n")
print("1. Page View:")
example_page_view()
print("\n2. Purchase:")
example_purchase()
print("\n3. User Signup:")
example_user_signup()
print("\n4. Batch Events:")
example_batch_events()
print("\n5. With Consent:")
example_with_consent()