diff --git a/backend/src/middleware/requestLogger.js b/backend/src/middleware/requestLogger.js index 0a02efa..20245d9 100644 --- a/backend/src/middleware/requestLogger.js +++ b/backend/src/middleware/requestLogger.js @@ -9,15 +9,16 @@ export function requestLogger(req, res, next) { const startAt = process.hrtime.bigint(); res.on('finish', () => { - const durationMs = Number(process.hrtime.bigint() - startAt) / 1e6; + const durationMs = parseFloat((Number(process.hrtime.bigint() - startAt) / 1e6).toFixed(2)); const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info'; logger[level]('http', { correlationId, method: req.method, + path: req.path, url: req.originalUrl, status: res.statusCode, - durationMs: durationMs.toFixed(2), + durationMs, contentLength: res.getHeader('content-length') ?? null, userAgent: req.headers['user-agent'] ?? null, }); diff --git a/backend/src/routes/multiSig.js b/backend/src/routes/multiSig.js index 6fa549d..1162a76 100644 --- a/backend/src/routes/multiSig.js +++ b/backend/src/routes/multiSig.js @@ -2,9 +2,22 @@ import express from 'express'; import { validate, rules } from '../middleware/validate.js'; import * as MultiSigService from '../services/multiSig.js'; import { broadcastToAccount } from '../services/websocket.js'; +import logger from '../config/logger.js'; const router = express.Router(); +function logError(req, error, context = {}) { + logger.error('route.error', { + requestId: req.id, + correlationId: req.correlationId, + method: req.method, + path: req.path, + ...context, + error: error.message, + stack: error.stack, + }); +} + /** * @swagger * /api/multisig/account/create: @@ -63,7 +76,8 @@ router.post('/account/create', rules.createMultiSig, validate, async (req, res) broadcastToAccount(result.publicKey, { type: 'multisig_created', ...result }); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error); + res.status(500).json({ error: 'Failed to create multi-sig account' }); } }); @@ -91,7 +105,8 @@ router.get('/account/:publicKey', rules.publicKeyParam, validate, async (req, re const config = await MultiSigService.getMultiSigConfig(req.params.publicKey); res.json(config); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to retrieve multi-sig configuration' }); } }); @@ -132,7 +147,8 @@ router.post('/account/update', rules.updateMultiSig, validate, async (req, res) const result = await MultiSigService.updateMultiSigConfig(sourceSecret, updates); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error); + res.status(500).json({ error: 'Failed to update multi-sig configuration' }); } }); @@ -177,7 +193,8 @@ router.post('/transaction/build', rules.buildMultiSigTx, validate, async (req, r broadcastToAccount(sourcePublicKey, { type: 'multisig_tx_pending', ...result }); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { destination: req.body.destination, amount: req.body.amount }); + res.status(500).json({ error: 'Failed to build multi-sig transaction' }); } }); @@ -211,7 +228,8 @@ router.post('/transaction/sign', rules.signMultiSigTx, validate, async (req, res const result = await MultiSigService.addSignature(txId, signerSecret); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { txId: req.body.txId }); + res.status(500).json({ error: 'Failed to add signature' }); } }); @@ -244,7 +262,8 @@ router.post('/transaction/submit', rules.submitMultiSigTx, validate, async (req, broadcastToAccount(result.hash, { type: 'multisig_tx_submitted', ...result }); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { txId: req.body.txId }); + res.status(500).json({ error: 'Failed to submit multi-sig transaction' }); } }); @@ -280,7 +299,8 @@ router.post('/transaction/verify', rules.verifyMultiSigTx, validate, async (req, const result = MultiSigService.verifySignatures(txXdr, expectedSigners); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error); + res.status(500).json({ error: 'Failed to verify signatures' }); } }); @@ -307,7 +327,8 @@ router.get('/transaction/pending/:publicKey', rules.publicKeyParam, validate, as const transactions = MultiSigService.getPendingTransactions(req.params.publicKey); res.json({ transactions }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to retrieve pending transactions' }); } }); @@ -337,7 +358,8 @@ router.get('/transaction/:txId', async (req, res) => { if (!tx) return res.status(404).json({ error: 'Transaction not found' }); res.json(tx); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { txId: req.params.txId }); + res.status(500).json({ error: 'Failed to retrieve transaction' }); } }); diff --git a/backend/src/routes/stellar.js b/backend/src/routes/stellar.js index 4c2d223..f9266d2 100644 --- a/backend/src/routes/stellar.js +++ b/backend/src/routes/stellar.js @@ -12,10 +12,23 @@ import { cacheMiddleware } from '../middleware/cache.js'; import { keys as cacheKeys, TTL, invalidateBalance } from '../cache/appCache.js'; import prisma from '../db/client.js'; import { getSubscriptionByPublicKey, sendWebPush } from '../notifications/webPush.js'; +import logger from '../config/logger.js'; import { createRateLimiter } from '../middleware/rateLimiter.js'; const router = express.Router(); +function logError(req, error, context = {}) { + logger.error('route.error', { + requestId: req.id, + correlationId: req.correlationId, + method: req.method, + path: req.path, + ...context, + error: error.message, + stack: error.stack, + }); +} + /** * @swagger * /api/stellar/account/create: @@ -42,7 +55,8 @@ router.post('/account/create', async (req, res) => { const account = await StellarService.createAccount(); res.json(account); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error); + res.status(500).json({ error: 'Failed to create account' }); } }); @@ -52,7 +66,8 @@ router.post('/account/fund', rules.publicKeyBody, validate, async (req, res) => const result = await StellarService.fundAccount(req.body.publicKey); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.body.publicKey }); + res.status(500).json({ error: 'Failed to fund account' }); } }); @@ -105,7 +120,8 @@ router.get('/account/:publicKey', rules.publicKeyParam, validate, const balance = await StellarService.getBalance(req.params.publicKey); res.json(balance); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to retrieve balance' }); } } ); @@ -176,7 +192,8 @@ router.post('/payment/send', paymentRateLimiter, rules.sendPayment, validate, as res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { destination: req.body.destination, amount: req.body.amount, assetCode: req.body.assetCode }); + res.status(500).json({ error: 'Failed to send payment' }); } }); @@ -230,7 +247,8 @@ router.get('/account/:publicKey/transactions', rules.publicKeyParam, validate, a } res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to retrieve transactions' }); } }); @@ -238,7 +256,8 @@ router.get('/fee-stats', cacheMiddleware(TTL.FEE_STATS, () => cacheKeys.feeStats try { res.json(await StellarService.getFeeStats()); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error); + res.status(500).json({ error: 'Failed to retrieve fee stats' }); } }); @@ -253,7 +272,8 @@ router.get('/exchange-rate/:from/:to', rules.assetCodeParams, validate, } res.json({ from, to, rate }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { from: req.params.from, to: req.params.to }); + res.status(500).json({ error: 'Failed to retrieve exchange rate' }); } } ); @@ -264,7 +284,8 @@ router.get('/rates', async (req, res) => { const rates = await getAllRates(); res.json({ rates }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error); + res.status(500).json({ error: 'Failed to retrieve rates' }); } }); @@ -276,7 +297,8 @@ router.get('/convert/:from/:to/:amount', rules.assetCodeParams, validate, async const result = await convert(amount, req.params.from, req.params.to); res.json({ from: req.params.from, to: req.params.to, amount, converted: result }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { from: req.params.from, to: req.params.to, amount: req.params.amount }); + res.status(500).json({ error: 'Failed to convert amount' }); } }); @@ -285,7 +307,8 @@ router.get('/network/status', async (req, res) => { const status = await StellarService.getNetworkStatus(); res.json(status); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error); + res.status(500).json({ error: 'Failed to retrieve network status' }); } }); @@ -375,7 +398,8 @@ router.post('/trustline', rules.createTrustline, validate, async (req, res) => { const result = await StellarService.createTrustline(sourceSecret, assetCode); res.json(result); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { assetCode: req.body.assetCode }); + res.status(500).json({ error: 'Failed to create trustline' }); } }); @@ -387,7 +411,8 @@ router.get('/account/:publicKey/label', rules.publicKeyParam, validate, async (r }); res.json({ accountLabel: user?.settings?.accountLabel ?? null }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to retrieve account label' }); } }); @@ -409,7 +434,8 @@ router.put('/account/:publicKey/label', rules.publicKeyParam, validate, }); res.json({ accountLabel: accountLabel || null }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to update account label' }); } } ); @@ -429,7 +455,8 @@ router.get('/account/:publicKey/settings', rules.publicKeyParam, validate, async kycSubmittedAt: user?.kycRecord?.submittedAt ?? null, }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to retrieve account settings' }); } }); @@ -457,7 +484,8 @@ router.put('/account/:publicKey/settings', }); res.json({ defaultAsset: settings.defaultAsset, notificationsOn: settings.notificationsOn }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { publicKey: req.params.publicKey }); + res.status(500).json({ error: 'Failed to update account settings' }); } } ); diff --git a/backend/src/routes/transactions.js b/backend/src/routes/transactions.js index a9fb904..5dfd6c4 100644 --- a/backend/src/routes/transactions.js +++ b/backend/src/routes/transactions.js @@ -2,9 +2,22 @@ import express from 'express'; import { transactionService } from '../services/transactions.js'; import { validate, rules } from '../middleware/validate.js'; import { broadcastToAccount } from '../services/websocket.js'; +import logger from '../config/logger.js'; const router = express.Router(); +function logError(req, error, context = {}) { + logger.error('route.error', { + requestId: req.id, + correlationId: req.correlationId, + method: req.method, + path: req.path, + ...context, + error: error.message, + stack: error.stack, + }); +} + /** * @swagger * /api/transactions/{accountId}: @@ -89,7 +102,8 @@ router.get('/:accountId', rules.accountIdParam, validate, async (req, res) => { const transactions = await transactionService.getTransactions(accountId, options); res.json(transactions); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { accountId: req.params.accountId }); + res.status(500).json({ error: 'Failed to retrieve transactions' }); } }); @@ -146,7 +160,8 @@ router.get('/:accountId/search', rules.accountIdParam, validate, async (req, res const results = await transactionService.searchTransactions(accountId, searchTerm, { limit }); res.json(results); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { accountId: req.params.accountId }); + res.status(500).json({ error: 'Failed to search transactions' }); } }); @@ -188,7 +203,8 @@ router.get('/:accountId/analytics', rules.accountIdParam, validate, async (req, const analytics = await transactionService.getTransactionAnalytics(accountId, timeframe); res.json(analytics); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { accountId: req.params.accountId }); + res.status(500).json({ error: 'Failed to retrieve transaction analytics' }); } }); @@ -229,7 +245,8 @@ router.get('/:accountId/latest', rules.accountIdParam, validate, async (req, res res.json(transaction); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { accountId: req.params.accountId }); + res.status(500).json({ error: 'Failed to retrieve latest transaction' }); } }); @@ -259,7 +276,8 @@ router.post('/:accountId/monitor', rules.accountIdParam, validate, async (req, r transactionService.startMonitoring(accountId); res.json({ message: 'Transaction monitoring started' }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { accountId: req.params.accountId }); + res.status(500).json({ error: 'Failed to start transaction monitoring' }); } }); @@ -289,8 +307,9 @@ router.delete('/:accountId/monitor', rules.accountIdParam, validate, async (req, transactionService.stopMonitoring(req.params.accountId); res.json({ message: 'Transaction monitoring stopped' }); } catch (error) { - res.status(500).json({ error: error.message }); + logError(req, error, { accountId: req.params.accountId }); + res.status(500).json({ error: 'Failed to stop transaction monitoring' }); } }); -export default router; \ No newline at end of file +export default router;