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 logging
import os
from datetime import datetime, timedelta, timezone, date
from typing import Optional, Dict
@@ -20,6 +21,9 @@ from .database import TimescaleDBClient
from .rate_limiter import MetaRateLimiter
from .token_manager import MetaTokenManager
# Set up logger
logger = logging.getLogger(__name__)
class ScheduledInsightsGrabber:
"""
@@ -340,11 +344,19 @@ class ScheduledInsightsGrabber:
for campaign in campaigns:
campaign_id = campaign['id']
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
if not campaign_name:
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(
campaign_id=campaign_id,
@@ -358,6 +370,7 @@ class ScheduledInsightsGrabber:
print(f" {count} campaigns cached for {account_id}")
if 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):
"""
@@ -1201,6 +1214,16 @@ class ScheduledInsightsGrabber:
async def async_main():
"""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:
# Initialize with max_accounts=3 for conservative start
# 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())