06/01 Add teller response log

This commit is contained in:
2026-06-01 09:46:56 -04:00
parent 95d04c6058
commit 947176f133
3 changed files with 63 additions and 15 deletions
+9
View File
@@ -1,3 +1,4 @@
import logging
import os import os
from flask import Flask from flask import Flask
from app.config import config from app.config import config
@@ -13,6 +14,14 @@ def create_app(config_name=None):
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config[config_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) db.init_app(app)
login_manager.init_app(app) login_manager.init_app(app)
migrate.init_app(app, db) migrate.init_app(app, db)
+3 -2
View File
@@ -68,7 +68,7 @@ def callback():
try: try:
teller_accounts = get_accounts(access_token) teller_accounts = get_accounts(access_token)
except Exception as e: 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 return jsonify({'error': f'Could not fetch accounts: {e}'}), 502
institution_name = '' institution_name = ''
@@ -176,6 +176,7 @@ def sync_preview_view(teller_account_id):
try: try:
preview = sync_preview(ta) preview = sync_preview(ta)
except Exception as e: 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') flash(f'Sync failed: {e}', 'danger')
return redirect(url_for('teller.index')) return redirect(url_for('teller.index'))
@@ -262,7 +263,7 @@ def refresh_balance(teller_account_id):
db.session.commit() db.session.commit()
return jsonify({'balance': available, 'status': 'ok'}) return jsonify({'balance': available, 'status': 'ok'})
except Exception as e: 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 return jsonify({'error': str(e)}), 502
+51 -13
View File
@@ -78,6 +78,8 @@ def _session(access_token):
cert_path = current_app.config.get('TELLER_CERT_PATH', '') cert_path = current_app.config.get('TELLER_CERT_PATH', '')
key_path = current_app.config.get('TELLER_KEY_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: if not cert_path or not key_path:
raise ValueError('TELLER_CERT_PATH and TELLER_KEY_PATH must be set in .env') raise ValueError('TELLER_CERT_PATH and TELLER_KEY_PATH must be set in .env')
if not os.path.exists(cert_path): if not os.path.exists(cert_path):
@@ -95,22 +97,47 @@ def _session(access_token):
return session 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): def get_accounts(access_token):
""" """
Fetch all accounts for an enrollment. Fetch all accounts for an enrollment.
Returns list of account dicts or raises on error. Returns list of account dicts or raises on error.
""" """
log.info('[teller] get_accounts: requesting %s/accounts', TELLER_BASE)
session = _session(access_token) session = _session(access_token)
resp = session.get(f'{TELLER_BASE}/accounts', timeout=15) try:
resp.raise_for_status() resp = session.get(f'{TELLER_BASE}/accounts', timeout=15)
return resp.json() 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): def get_balance(access_token, account_id):
"""Fetch live balance for a single account.""" """Fetch live balance for a single account."""
log.info('[teller] get_balance: account=%s', account_id)
session = _session(access_token) session = _session(access_token)
resp = session.get(f'{TELLER_BASE}/accounts/{account_id}/balances', timeout=15) try:
resp.raise_for_status() 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() return resp.json()
@@ -132,13 +159,20 @@ def get_transactions(access_token, account_id, start_date=None, end_date=None,
if count: if count:
params['count'] = count params['count'] = count
resp = session.get( log.info('[teller] get_transactions: account=%s params=%s', account_id, params)
f'{TELLER_BASE}/accounts/{account_id}/transactions', try:
params=params, resp = session.get(
timeout=30, f'{TELLER_BASE}/accounts/{account_id}/transactions',
) params=params,
resp.raise_for_status() timeout=30,
return resp.json() )
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): 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, end_date=today,
) )
except requests.exceptions.HTTPError as e: 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 raise
cat_map = build_category_map() cat_map = build_category_map()