06/01 Add teller response log
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
from flask import Flask
|
||||
from app.config import config
|
||||
@@ -13,6 +14,14 @@ def create_app(config_name=None):
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config[config_name])
|
||||
|
||||
# Ensure app-level loggers (services, routes) emit INFO+ to stderr/Gunicorn
|
||||
if not app.debug and not logging.root.handlers:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s %(name)s: %(message)s',
|
||||
)
|
||||
logging.getLogger('app').setLevel(logging.INFO)
|
||||
|
||||
db.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
||||
@@ -68,7 +68,7 @@ def callback():
|
||||
try:
|
||||
teller_accounts = get_accounts(access_token)
|
||||
except Exception as e:
|
||||
log.error(f'[teller] get_accounts failed: {e}')
|
||||
log.error('[teller] get_accounts failed during callback: %s', e, exc_info=True)
|
||||
return jsonify({'error': f'Could not fetch accounts: {e}'}), 502
|
||||
|
||||
institution_name = ''
|
||||
@@ -176,6 +176,7 @@ def sync_preview_view(teller_account_id):
|
||||
try:
|
||||
preview = sync_preview(ta)
|
||||
except Exception as e:
|
||||
log.error('[teller] sync_preview failed for teller_account_id=%s: %s', teller_account_id, e, exc_info=True)
|
||||
flash(f'Sync failed: {e}', 'danger')
|
||||
return redirect(url_for('teller.index'))
|
||||
|
||||
@@ -262,7 +263,7 @@ def refresh_balance(teller_account_id):
|
||||
db.session.commit()
|
||||
return jsonify({'balance': available, 'status': 'ok'})
|
||||
except Exception as e:
|
||||
log.error(f'[teller] balance refresh failed: {e}')
|
||||
log.error('[teller] balance refresh failed for teller_account_id=%s: %s', teller_account_id, e, exc_info=True)
|
||||
return jsonify({'error': str(e)}), 502
|
||||
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ def _session(access_token):
|
||||
cert_path = current_app.config.get('TELLER_CERT_PATH', '')
|
||||
key_path = current_app.config.get('TELLER_KEY_PATH', '')
|
||||
|
||||
log.debug(f'[teller] _session: cert={cert_path!r} key={key_path!r}')
|
||||
|
||||
if not cert_path or not key_path:
|
||||
raise ValueError('TELLER_CERT_PATH and TELLER_KEY_PATH must be set in .env')
|
||||
if not os.path.exists(cert_path):
|
||||
@@ -95,22 +97,47 @@ def _session(access_token):
|
||||
return session
|
||||
|
||||
|
||||
def _raise_for_status_with_log(resp, context=''):
|
||||
"""Call raise_for_status() but log the response body first on error."""
|
||||
if not resp.ok:
|
||||
log.error(
|
||||
'[teller] API error%s — status=%s url=%s body=%r',
|
||||
f' ({context})' if context else '',
|
||||
resp.status_code,
|
||||
resp.url,
|
||||
resp.text[:2000],
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
|
||||
def get_accounts(access_token):
|
||||
"""
|
||||
Fetch all accounts for an enrollment.
|
||||
Returns list of account dicts or raises on error.
|
||||
"""
|
||||
log.info('[teller] get_accounts: requesting %s/accounts', TELLER_BASE)
|
||||
session = _session(access_token)
|
||||
resp = session.get(f'{TELLER_BASE}/accounts', timeout=15)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
try:
|
||||
resp = session.get(f'{TELLER_BASE}/accounts', timeout=15)
|
||||
except requests.exceptions.RequestException as e:
|
||||
log.error('[teller] get_accounts: request failed: %s', e, exc_info=True)
|
||||
raise
|
||||
_raise_for_status_with_log(resp, 'get_accounts')
|
||||
data = resp.json()
|
||||
log.info('[teller] get_accounts: returned %d account(s)', len(data))
|
||||
return data
|
||||
|
||||
|
||||
def get_balance(access_token, account_id):
|
||||
"""Fetch live balance for a single account."""
|
||||
log.info('[teller] get_balance: account=%s', account_id)
|
||||
session = _session(access_token)
|
||||
resp = session.get(f'{TELLER_BASE}/accounts/{account_id}/balances', timeout=15)
|
||||
resp.raise_for_status()
|
||||
try:
|
||||
resp = session.get(f'{TELLER_BASE}/accounts/{account_id}/balances', timeout=15)
|
||||
except requests.exceptions.RequestException as e:
|
||||
log.error('[teller] get_balance: request failed: %s', e, exc_info=True)
|
||||
raise
|
||||
_raise_for_status_with_log(resp, f'get_balance account={account_id}')
|
||||
return resp.json()
|
||||
|
||||
|
||||
@@ -132,13 +159,20 @@ def get_transactions(access_token, account_id, start_date=None, end_date=None,
|
||||
if count:
|
||||
params['count'] = count
|
||||
|
||||
resp = session.get(
|
||||
f'{TELLER_BASE}/accounts/{account_id}/transactions',
|
||||
params=params,
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
log.info('[teller] get_transactions: account=%s params=%s', account_id, params)
|
||||
try:
|
||||
resp = session.get(
|
||||
f'{TELLER_BASE}/accounts/{account_id}/transactions',
|
||||
params=params,
|
||||
timeout=30,
|
||||
)
|
||||
except requests.exceptions.RequestException as e:
|
||||
log.error('[teller] get_transactions: request failed: %s', e, exc_info=True)
|
||||
raise
|
||||
_raise_for_status_with_log(resp, f'get_transactions account={account_id}')
|
||||
data = resp.json()
|
||||
log.info('[teller] get_transactions: returned %d transaction(s)', len(data))
|
||||
return data
|
||||
|
||||
|
||||
def parse_transaction(teller_txn, pfm_account_id, category_id_map):
|
||||
@@ -218,7 +252,11 @@ def sync_preview(teller_account, days_back=90):
|
||||
end_date=today,
|
||||
)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
log.error(f'[teller] fetch failed for {teller_account.teller_account_id}: {e}')
|
||||
body = e.response.text[:2000] if e.response is not None else '(no response)'
|
||||
log.error(
|
||||
'[teller] fetch failed for %s: %s — body=%r',
|
||||
teller_account.teller_account_id, e, body, exc_info=True,
|
||||
)
|
||||
raise
|
||||
|
||||
cat_map = build_category_map()
|
||||
|
||||
Reference in New Issue
Block a user