Added some logging

This commit is contained in:
Jonas Linter
2025-10-28 15:25:42 +01:00
parent 805c90f76e
commit 845a130aad
4 changed files with 341 additions and 1 deletions

View File

@@ -4,6 +4,7 @@ Runs periodically to build time-series data for dashboards.
""" """
import asyncio import asyncio
import logging
import os import os
from datetime import datetime, timedelta, timezone, date from datetime import datetime, timedelta, timezone, date
from typing import Optional, Dict from typing import Optional, Dict
@@ -20,6 +21,9 @@ from .database import TimescaleDBClient
from .rate_limiter import MetaRateLimiter from .rate_limiter import MetaRateLimiter
from .token_manager import MetaTokenManager from .token_manager import MetaTokenManager
# Set up logger
logger = logging.getLogger(__name__)
class ScheduledInsightsGrabber: class ScheduledInsightsGrabber:
""" """
@@ -340,11 +344,19 @@ class ScheduledInsightsGrabber:
for campaign in campaigns: for campaign in campaigns:
campaign_id = campaign['id'] campaign_id = campaign['id']
campaign_name = campaign.get('name') campaign_name = campaign.get('name')
campaign_dict = dict(campaign)
# DEBUG: Log all campaign data before upsert
logger.info(
f"Campaign metadata before upsert: id={campaign_id}, "
f"name={campaign_name!r}, status={campaign.get('status')}, "
f"objective={campaign.get('objective')}, raw={campaign_dict}"
)
# Track campaigns without names for debugging # Track campaigns without names for debugging
if not campaign_name: if not campaign_name:
campaigns_without_name.append(campaign_id) campaigns_without_name.append(campaign_id)
print(f" WARNING: Campaign {campaign_id} has no name. Raw data: {dict(campaign)}") logger.warning(f"Campaign {campaign_id} has no name. Raw data: {campaign_dict}")
await self.db.upsert_campaign( await self.db.upsert_campaign(
campaign_id=campaign_id, campaign_id=campaign_id,
@@ -358,6 +370,7 @@ class ScheduledInsightsGrabber:
print(f" {count} campaigns cached for {account_id}") print(f" {count} campaigns cached for {account_id}")
if campaigns_without_name: if campaigns_without_name:
print(f" ⚠️ {len(campaigns_without_name)} campaigns without names: {campaigns_without_name}") print(f" ⚠️ {len(campaigns_without_name)} campaigns without names: {campaigns_without_name}")
logger.warning(f"{len(campaigns_without_name)} campaigns without names: {campaigns_without_name}")
async def cache_adsets_metadata(self, account_id: str, limit: int = 100): async def cache_adsets_metadata(self, account_id: str, limit: int = 100):
""" """
@@ -1201,6 +1214,16 @@ class ScheduledInsightsGrabber:
async def async_main(): async def async_main():
"""Async main entry point for scheduled grabber.""" """Async main entry point for scheduled grabber."""
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('meta_api_grabber.log'),
logging.StreamHandler()
]
)
try: try:
# Initialize with max_accounts=3 for conservative start # Initialize with max_accounts=3 for conservative start
# Set max_accounts=None to process all accessible accounts # Set max_accounts=None to process all accessible accounts

View File

@@ -0,0 +1,91 @@
"""
Test to see what campaign insights API actually returns for campaign_name.
"""
import os
import json
from dotenv import load_dotenv
from facebook_business.api import FacebookAdsApi
from facebook_business.adobjects.adaccount import AdAccount
from facebook_business.adobjects.adsinsights import AdsInsights
# Load environment variables
load_dotenv()
# Initialize Facebook Ads API
access_token = os.getenv("META_ACCESS_TOKEN")
app_secret = os.getenv("META_APP_SECRET")
app_id = os.getenv("META_APP_ID")
if not all([access_token, app_secret, app_id]):
raise ValueError("Missing required environment variables")
FacebookAdsApi.init(
app_id=app_id,
app_secret=app_secret,
access_token=access_token,
)
account_id = "act_238334370765317"
print(f"Testing campaign INSIGHTS fetch for account: {account_id}")
print("=" * 60)
# Get campaign insights (this is what scheduled_grabber.py does)
print("\nFetching campaign-level insights with 'today' preset...")
print("-" * 60)
fields = [
AdsInsights.Field.campaign_id,
AdsInsights.Field.campaign_name,
AdsInsights.Field.impressions,
AdsInsights.Field.clicks,
AdsInsights.Field.spend,
AdsInsights.Field.ctr,
AdsInsights.Field.cpc,
AdsInsights.Field.cpm,
AdsInsights.Field.reach,
AdsInsights.Field.actions,
AdsInsights.Field.date_start,
AdsInsights.Field.date_stop,
]
params = {
"date_preset": "today",
"level": "campaign",
"limit": 10,
}
ad_account = AdAccount(account_id)
insights = ad_account.get_insights(
fields=fields,
params=params,
)
insights_list = list(insights)
print(f"Found {len(insights_list)} campaign insights\n")
campaigns_without_names = []
for i, insight in enumerate(insights_list, 1):
insight_dict = dict(insight)
campaign_id = insight_dict.get('campaign_id')
campaign_name = insight_dict.get('campaign_name')
print(f"Campaign Insight {i}:")
print(f" Campaign ID: {campaign_id}")
print(f" Campaign Name: {campaign_name if campaign_name else '❌ MISSING'}")
print(f" Impressions: {insight_dict.get('impressions')}")
print(f" Spend: {insight_dict.get('spend')}")
print(f" Keys available: {list(insight_dict.keys())}")
print(f" Raw JSON: {json.dumps(insight_dict, indent=4, default=str)}")
print()
if not campaign_name:
campaigns_without_names.append(campaign_id)
print("=" * 60)
if campaigns_without_names:
print(f"⚠️ {len(campaigns_without_names)} campaigns WITHOUT names in insights:")
print(f" {campaigns_without_names}")
else:
print("✓ All campaigns have names in insights!")

View File

@@ -0,0 +1,118 @@
"""
Test script to diagnose campaign name issues for a specific ad account.
"""
import os
import json
from dotenv import load_dotenv
from facebook_business.api import FacebookAdsApi
from facebook_business.adobjects.adaccount import AdAccount
from facebook_business.adobjects.campaign import Campaign
# Load environment variables
load_dotenv()
# Initialize Facebook Ads API
access_token = os.getenv("META_ACCESS_TOKEN")
app_secret = os.getenv("META_APP_SECRET")
app_id = os.getenv("META_APP_ID")
if not all([access_token, app_secret, app_id]):
raise ValueError("Missing required environment variables")
FacebookAdsApi.init(
app_id=app_id,
app_secret=app_secret,
access_token=access_token,
)
# Test account ID
account_id = "act_238334370765317"
print(f"Testing campaign fetch for account: {account_id}")
print("=" * 60)
# Get campaigns using get_campaigns
print("\n1. Testing AdAccount.get_campaigns()...")
print("-" * 60)
ad_account = AdAccount(account_id)
campaigns = ad_account.get_campaigns(
fields=[
Campaign.Field.name,
Campaign.Field.status,
Campaign.Field.objective,
Campaign.Field.id,
],
params={'limit': 10}
)
campaigns_list = list(campaigns)
print(f"Found {len(campaigns_list)} campaigns\n")
campaigns_without_names = []
for i, campaign in enumerate(campaigns_list, 1):
campaign_dict = dict(campaign)
campaign_id = campaign_dict.get('id')
campaign_name = campaign_dict.get('name')
print(f"Campaign {i}:")
print(f" ID: {campaign_id}")
print(f" Name: {campaign_name if campaign_name else '❌ MISSING'}")
print(f" Status: {campaign_dict.get('status')}")
print(f" Objective: {campaign_dict.get('objective')}")
print(f" Raw data: {json.dumps(campaign_dict, indent=4)}")
print()
if not campaign_name:
campaigns_without_names.append(campaign_id)
# If we found campaigns without names, try fetching them individually
if campaigns_without_names:
print("\n2. Retrying campaigns without names (individual fetch)...")
print("-" * 60)
for campaign_id in campaigns_without_names:
print(f"\nFetching campaign {campaign_id} directly...")
try:
campaign_obj = Campaign(campaign_id)
campaign_data = campaign_obj.api_get(
fields=[
Campaign.Field.name,
Campaign.Field.status,
Campaign.Field.objective,
Campaign.Field.account_id,
]
)
campaign_dict = dict(campaign_data)
print(f" Direct fetch result:")
print(f" Name: {campaign_dict.get('name', '❌ STILL MISSING')}")
print(f" Status: {campaign_dict.get('status')}")
print(f" Raw data: {json.dumps(campaign_dict, indent=4)}")
except Exception as e:
print(f" Error fetching campaign: {e}")
print("\n" + "=" * 60)
print(f"Summary: {len(campaigns_without_names)} out of {len(campaigns_list)} campaigns have missing names")
print(f"Missing campaign IDs: {campaigns_without_names}")
else:
print("\n" + "=" * 60)
print("✓ All campaigns have names!")
print("\n3. Checking account permissions...")
print("-" * 60)
try:
account_data = ad_account.api_get(
fields=['name', 'account_status', 'capabilities', 'business']
)
account_dict = dict(account_data)
print(f"Account Name: {account_dict.get('name')}")
print(f"Account Status: {account_dict.get('account_status')}")
print(f"Capabilities: {json.dumps(account_dict.get('capabilities', []), indent=2)}")
print(f"Business ID: {account_dict.get('business')}")
except Exception as e:
print(f"Error fetching account details: {e}")
print("\n" + "=" * 60)
print("Test complete!")

View File

@@ -0,0 +1,108 @@
"""
Test to diagnose if the rate limiter is causing campaign name issues.
"""
import os
import asyncio
from dotenv import load_dotenv
from facebook_business.api import FacebookAdsApi
from facebook_business.adobjects.adaccount import AdAccount
from facebook_business.adobjects.campaign import Campaign
from rate_limiter import MetaRateLimiter
# Load environment variables
load_dotenv()
# Initialize Facebook Ads API
access_token = os.getenv("META_ACCESS_TOKEN")
app_secret = os.getenv("META_APP_SECRET")
app_id = os.getenv("META_APP_ID")
if not all([access_token, app_secret, app_id]):
raise ValueError("Missing required environment variables")
FacebookAdsApi.init(
app_id=app_id,
app_secret=app_secret,
access_token=access_token,
)
account_id = "act_238334370765317"
# Initialize rate limiter
rate_limiter = MetaRateLimiter(
base_delay=0.1, # Fast for testing
throttle_threshold=75.0,
max_retry_delay=300.0,
max_retries=5,
)
async def _rate_limited_request(func, *args, **kwargs):
"""Execute a request with rate limiting (same as in scheduled_grabber.py)."""
return await rate_limiter.execute_with_retry(func, *args, **kwargs)
async def test_direct_fetch():
"""Test direct fetch without rate limiter."""
print("1. DIRECT FETCH (no rate limiter)")
print("=" * 60)
ad_account = AdAccount(account_id)
campaigns = ad_account.get_campaigns(
fields=[
Campaign.Field.name,
Campaign.Field.status,
Campaign.Field.objective,
],
params={'limit': 5}
)
print(f"Type of campaigns: {type(campaigns)}")
print(f"Campaigns object: {campaigns}\n")
for i, campaign in enumerate(campaigns, 1):
campaign_dict = dict(campaign)
print(f"Campaign {i}:")
print(f" ID: {campaign_dict.get('id')}")
print(f" Name: {campaign_dict.get('name', '❌ MISSING')}")
print(f" Keys in dict: {list(campaign_dict.keys())}")
print()
async def test_rate_limited_fetch():
"""Test fetch WITH rate limiter."""
print("\n2. RATE LIMITED FETCH (with rate limiter)")
print("=" * 60)
ad_account = AdAccount(account_id)
campaigns = await _rate_limited_request(
ad_account.get_campaigns,
fields=[
Campaign.Field.name,
Campaign.Field.status,
Campaign.Field.objective,
],
params={'limit': 5}
)
print(f"Type of campaigns: {type(campaigns)}")
print(f"Campaigns object: {campaigns}\n")
for i, campaign in enumerate(campaigns, 1):
campaign_dict = dict(campaign)
print(f"Campaign {i}:")
print(f" ID: {campaign_dict.get('id')}")
print(f" Name: {campaign_dict.get('name', '❌ MISSING')}")
print(f" Keys in dict: {list(campaign_dict.keys())}")
print()
async def main():
await test_direct_fetch()
await test_rate_limited_fetch()
if __name__ == "__main__":
asyncio.run(main())