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
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)
+3 -2
View File
@@ -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
+51 -13
View File
@@ -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()