From 947176f133a1581e605cf9763a2f23944c662e43 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Mon, 1 Jun 2026 09:46:56 -0400 Subject: [PATCH] 06/01 Add teller response log --- app/__init__.py | 9 +++++ app/routes/teller.py | 5 +-- app/services/teller_service.py | 64 +++++++++++++++++++++++++++------- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 65023d7..168263d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -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) diff --git a/app/routes/teller.py b/app/routes/teller.py index e1a0d30..6a0ad5a 100644 --- a/app/routes/teller.py +++ b/app/routes/teller.py @@ -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 diff --git a/app/services/teller_service.py b/app/services/teller_service.py index 3852b9e..7477207 100644 --- a/app/services/teller_service.py +++ b/app/services/teller_service.py @@ -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()