06/01 Add teller response log
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user