refactor: use safe request-scoped backend error logging

This commit is contained in:
Nico 2026-02-16 01:36:39 -08:00
parent e2e9ec9eb4
commit 9cb0ac19e5
7 changed files with 66 additions and 44 deletions

View File

@ -1,6 +1,7 @@
const householdModel = require("../models/household.model"); const householdModel = require("../models/household.model");
const { sendError } = require("../utils/http"); const { sendError } = require("../utils/http");
const { inviteCodeLast4, safeErrorMessage } = require("../utils/redaction"); const { inviteCodeLast4 } = require("../utils/redaction");
const { logError } = require("../utils/logger");
// Get all households user belongs to // Get all households user belongs to
exports.getUserHouseholds = async (req, res) => { exports.getUserHouseholds = async (req, res) => {
@ -8,7 +9,7 @@ exports.getUserHouseholds = async (req, res) => {
const households = await householdModel.getUserHouseholds(req.user.id); const households = await householdModel.getUserHouseholds(req.user.id);
res.json(households); res.json(households);
} catch (error) { } catch (error) {
console.error("Get user households error:", error); logError(req, "households.getUserHouseholds", error);
sendError(res, 500, "Failed to fetch households"); sendError(res, 500, "Failed to fetch households");
} }
}; };
@ -27,7 +28,7 @@ exports.getHousehold = async (req, res) => {
res.json(household); res.json(household);
} catch (error) { } catch (error) {
console.error("Get household error:", error); logError(req, "households.getHousehold", error);
sendError(res, 500, "Failed to fetch household"); sendError(res, 500, "Failed to fetch household");
} }
}; };
@ -55,7 +56,7 @@ exports.createHousehold = async (req, res) => {
household household
}); });
} catch (error) { } catch (error) {
console.error("Create household error:", error); logError(req, "households.createHousehold", error);
sendError(res, 500, "Failed to create household"); sendError(res, 500, "Failed to create household");
} }
}; };
@ -83,7 +84,7 @@ exports.updateHousehold = async (req, res) => {
household household
}); });
} catch (error) { } catch (error) {
console.error("Update household error:", error); logError(req, "households.updateHousehold", error);
sendError(res, 500, "Failed to update household"); sendError(res, 500, "Failed to update household");
} }
}; };
@ -94,7 +95,7 @@ exports.deleteHousehold = async (req, res) => {
await householdModel.deleteHousehold(req.params.householdId); await householdModel.deleteHousehold(req.params.householdId);
res.json({ message: "Household deleted successfully" }); res.json({ message: "Household deleted successfully" });
} catch (error) { } catch (error) {
console.error("Delete household error:", error); logError(req, "households.deleteHousehold", error);
sendError(res, 500, "Failed to delete household"); sendError(res, 500, "Failed to delete household");
} }
}; };
@ -108,11 +109,9 @@ exports.refreshInviteCode = async (req, res) => {
household household
}); });
} catch (error) { } catch (error) {
console.error( logError(req, "households.refreshInviteCode", error, {
`Refresh invite code error request_id=${req.request_id} invite_last4=${inviteCodeLast4( invite_last4: inviteCodeLast4(req.body?.inviteCode),
req.body?.inviteCode });
)} message=${safeErrorMessage(error)}`
);
sendError(res, 500, "Failed to refresh invite code"); sendError(res, 500, "Failed to refresh invite code");
} }
}; };
@ -144,11 +143,7 @@ exports.joinHousehold = async (req, res) => {
household: { id: result.id, name: result.name } household: { id: result.id, name: result.name }
}); });
} catch (error) { } catch (error) {
console.error( logError(req, "households.joinHousehold", error, { invite_last4: inviteLast4 });
`Join household error request_id=${req.request_id} invite_last4=${inviteLast4} message=${safeErrorMessage(
error
)}`
);
sendError(res, 500, "Failed to join household"); sendError(res, 500, "Failed to join household");
} }
}; };
@ -159,7 +154,7 @@ exports.getMembers = async (req, res) => {
const members = await householdModel.getHouseholdMembers(req.params.householdId); const members = await householdModel.getHouseholdMembers(req.params.householdId);
res.json(members); res.json(members);
} catch (error) { } catch (error) {
console.error("Get members error:", error); logError(req, "households.getMembers", error);
sendError(res, 500, "Failed to fetch members"); sendError(res, 500, "Failed to fetch members");
} }
}; };
@ -190,7 +185,7 @@ exports.updateMemberRole = async (req, res) => {
member: updated member: updated
}); });
} catch (error) { } catch (error) {
console.error("Update member role error:", error); logError(req, "households.updateMemberRole", error);
sendError(res, 500, "Failed to update member role"); sendError(res, 500, "Failed to update member role");
} }
}; };
@ -210,7 +205,7 @@ exports.removeMember = async (req, res) => {
res.json({ message: "Member removed successfully" }); res.json({ message: "Member removed successfully" });
} catch (error) { } catch (error) {
console.error("Remove member error:", error); logError(req, "households.removeMember", error);
sendError(res, 500, "Failed to remove member"); sendError(res, 500, "Failed to remove member");
} }
}; };

View File

@ -1,6 +1,7 @@
const List = require("../models/list.model"); const List = require("../models/list.model");
const { isValidItemType, isValidItemGroup, isValidZone } = require("../constants/classifications"); const { isValidItemType, isValidItemGroup, isValidZone } = require("../constants/classifications");
const { sendError } = require("../utils/http"); const { sendError } = require("../utils/http");
const { logError } = require("../utils/logger");
exports.getList = async (req, res) => { exports.getList = async (req, res) => {
@ -114,7 +115,7 @@ exports.updateItemWithClassification = async (req, res) => {
res.json({ message: "Item updated successfully" }); res.json({ message: "Item updated successfully" });
} catch (error) { } catch (error) {
console.error("Error updating item with classification:", error); logError(req, "listsLegacy.updateItemWithClassification", error);
sendError(res, 500, "Failed to update item"); sendError(res, 500, "Failed to update item");
} }
}; };

View File

@ -1,6 +1,7 @@
const List = require("../models/list.model.v2"); const List = require("../models/list.model.v2");
const { isValidItemType, isValidItemGroup, isValidZone } = require("../constants/classifications"); const { isValidItemType, isValidItemGroup, isValidZone } = require("../constants/classifications");
const { sendError } = require("../utils/http"); const { sendError } = require("../utils/http");
const { logError } = require("../utils/logger");
/** /**
* Get list items for household and store * Get list items for household and store
@ -12,7 +13,7 @@ exports.getList = async (req, res) => {
const items = await List.getHouseholdStoreList(householdId, storeId); const items = await List.getHouseholdStoreList(householdId, storeId);
res.json({ items }); res.json({ items });
} catch (error) { } catch (error) {
console.error("Error getting list:", error); logError(req, "listsV2.getList", error);
sendError(res, 500, "Failed to get list"); sendError(res, 500, "Failed to get list");
} }
}; };
@ -37,7 +38,7 @@ exports.getItemByName = async (req, res) => {
res.json(item); res.json(item);
} catch (error) { } catch (error) {
console.error("Error getting item:", error); logError(req, "listsV2.getItemByName", error);
sendError(res, 500, "Failed to get item"); sendError(res, 500, "Failed to get item");
} }
}; };
@ -84,7 +85,7 @@ exports.addItem = async (req, res) => {
} }
}); });
} catch (error) { } catch (error) {
console.error("Error adding item:", error); logError(req, "listsV2.addItem", error);
sendError(res, 500, "Failed to add item"); sendError(res, 500, "Failed to add item");
} }
}; };
@ -109,7 +110,7 @@ exports.markBought = async (req, res) => {
res.json({ message: bought ? "Item marked as bought" : "Item unmarked" }); res.json({ message: bought ? "Item marked as bought" : "Item unmarked" });
} catch (error) { } catch (error) {
console.error("Error marking bought:", error); logError(req, "listsV2.markBought", error);
sendError(res, 500, "Failed to update item"); sendError(res, 500, "Failed to update item");
} }
}; };
@ -146,7 +147,7 @@ exports.updateItem = async (req, res) => {
} }
}); });
} catch (error) { } catch (error) {
console.error("Error updating item:", error); logError(req, "listsV2.updateItem", error);
sendError(res, 500, "Failed to update item"); sendError(res, 500, "Failed to update item");
} }
}; };
@ -174,7 +175,7 @@ exports.deleteItem = async (req, res) => {
res.json({ message: "Item deleted" }); res.json({ message: "Item deleted" });
} catch (error) { } catch (error) {
console.error("Error deleting item:", error); logError(req, "listsV2.deleteItem", error);
sendError(res, 500, "Failed to delete item"); sendError(res, 500, "Failed to delete item");
} }
}; };
@ -191,7 +192,7 @@ exports.getSuggestions = async (req, res) => {
const suggestions = await List.getSuggestions(query || "", householdId, storeId); const suggestions = await List.getSuggestions(query || "", householdId, storeId);
res.json(suggestions); res.json(suggestions);
} catch (error) { } catch (error) {
console.error("Error getting suggestions:", error); logError(req, "listsV2.getSuggestions", error);
sendError(res, 500, "Failed to get suggestions"); sendError(res, 500, "Failed to get suggestions");
} }
}; };
@ -206,7 +207,7 @@ exports.getRecentlyBought = async (req, res) => {
const items = await List.getRecentlyBoughtItems(householdId, storeId); const items = await List.getRecentlyBoughtItems(householdId, storeId);
res.json(items); res.json(items);
} catch (error) { } catch (error) {
console.error("Error getting recent items:", error); logError(req, "listsV2.getRecentlyBought", error);
sendError(res, 500, "Failed to get recent items"); sendError(res, 500, "Failed to get recent items");
} }
}; };
@ -233,7 +234,7 @@ exports.getClassification = async (req, res) => {
const classification = await List.getClassification(householdId, item.item_id); const classification = await List.getClassification(householdId, item.item_id);
res.json({ classification }); res.json({ classification });
} catch (error) { } catch (error) {
console.error("Error getting classification:", error); logError(req, "listsV2.getClassification", error);
sendError(res, 500, "Failed to get classification"); sendError(res, 500, "Failed to get classification");
} }
}; };
@ -290,7 +291,7 @@ exports.setClassification = async (req, res) => {
res.json({ message: "Classification set", classification }); res.json({ message: "Classification set", classification });
} catch (error) { } catch (error) {
console.error("Error setting classification:", error); logError(req, "listsV2.setClassification", error);
sendError(res, 500, "Failed to set classification"); sendError(res, 500, "Failed to set classification");
} }
}; };
@ -318,7 +319,7 @@ exports.updateItemImage = async (req, res) => {
res.json({ message: "Image updated successfully" }); res.json({ message: "Image updated successfully" });
} catch (error) { } catch (error) {
console.error("Error updating image:", error); logError(req, "listsV2.updateItemImage", error);
sendError(res, 500, "Failed to update image"); sendError(res, 500, "Failed to update image");
} }
}; };

View File

@ -1,5 +1,6 @@
const storeModel = require("../models/store.model"); const storeModel = require("../models/store.model");
const { sendError } = require("../utils/http"); const { sendError } = require("../utils/http");
const { logError } = require("../utils/logger");
// Get all available stores // Get all available stores
exports.getAllStores = async (req, res) => { exports.getAllStores = async (req, res) => {
@ -7,7 +8,7 @@ exports.getAllStores = async (req, res) => {
const stores = await storeModel.getAllStores(); const stores = await storeModel.getAllStores();
res.json(stores); res.json(stores);
} catch (error) { } catch (error) {
console.error("Get all stores error:", error); logError(req, "stores.getAllStores", error);
sendError(res, 500, "Failed to fetch stores"); sendError(res, 500, "Failed to fetch stores");
} }
}; };
@ -18,7 +19,7 @@ exports.getHouseholdStores = async (req, res) => {
const stores = await storeModel.getHouseholdStores(req.params.householdId); const stores = await storeModel.getHouseholdStores(req.params.householdId);
res.json(stores); res.json(stores);
} catch (error) { } catch (error) {
console.error("Get household stores error:", error); logError(req, "stores.getHouseholdStores", error);
sendError(res, 500, "Failed to fetch household stores"); sendError(res, 500, "Failed to fetch household stores");
} }
}; };
@ -48,7 +49,7 @@ exports.addStoreToHousehold = async (req, res) => {
store store
}); });
} catch (error) { } catch (error) {
console.error("Add store to household error:", error); logError(req, "stores.addStoreToHousehold", error);
sendError(res, 500, "Failed to add store to household"); sendError(res, 500, "Failed to add store to household");
} }
}; };
@ -63,7 +64,7 @@ exports.removeStoreFromHousehold = async (req, res) => {
res.json({ message: "Store removed from household successfully" }); res.json({ message: "Store removed from household successfully" });
} catch (error) { } catch (error) {
console.error("Remove store from household error:", error); logError(req, "stores.removeStoreFromHousehold", error);
sendError(res, 500, "Failed to remove store from household"); sendError(res, 500, "Failed to remove store from household");
} }
}; };
@ -78,7 +79,7 @@ exports.setDefaultStore = async (req, res) => {
res.json({ message: "Default store updated successfully" }); res.json({ message: "Default store updated successfully" });
} catch (error) { } catch (error) {
console.error("Set default store error:", error); logError(req, "stores.setDefaultStore", error);
sendError(res, 500, "Failed to set default store"); sendError(res, 500, "Failed to set default store");
} }
}; };
@ -99,7 +100,7 @@ exports.createStore = async (req, res) => {
store store
}); });
} catch (error) { } catch (error) {
console.error("Create store error:", error); logError(req, "stores.createStore", error);
if (error.code === '23505') { // Unique violation if (error.code === '23505') { // Unique violation
return sendError(res, 400, "Store with this name already exists"); return sendError(res, 400, "Store with this name already exists");
} }
@ -126,7 +127,7 @@ exports.updateStore = async (req, res) => {
store store
}); });
} catch (error) { } catch (error) {
console.error("Update store error:", error); logError(req, "stores.updateStore", error);
sendError(res, 500, "Failed to update store"); sendError(res, 500, "Failed to update store");
} }
}; };
@ -137,7 +138,7 @@ exports.deleteStore = async (req, res) => {
await storeModel.deleteStore(req.params.storeId); await storeModel.deleteStore(req.params.storeId);
res.json({ message: "Store deleted successfully" }); res.json({ message: "Store deleted successfully" });
} catch (error) { } catch (error) {
console.error("Delete store error:", error); logError(req, "stores.deleteStore", error);
if (error.message.includes('in use')) { if (error.message.includes('in use')) {
return sendError(res, 400, error.message); return sendError(res, 400, error.message);
} }

View File

@ -1,6 +1,7 @@
const User = require("../models/user.model"); const User = require("../models/user.model");
const bcrypt = require("bcryptjs"); const bcrypt = require("bcryptjs");
const { sendError } = require("../utils/http"); const { sendError } = require("../utils/http");
const { logError } = require("../utils/logger");
exports.test = async (req, res) => { exports.test = async (req, res) => {
console.log("User route is working"); console.log("User route is working");
@ -27,6 +28,7 @@ exports.updateUserRole = async (req, res) => {
res.json({ message: "Role updated", id, role }); res.json({ message: "Role updated", id, role });
} catch (err) { } catch (err) {
logError(req, "users.updateUserRole", err);
sendError(res, 500, "Failed to update role"); sendError(res, 500, "Failed to update role");
} }
}; };
@ -42,6 +44,7 @@ exports.deleteUser = async (req, res) => {
res.json({ message: "User deleted", id }); res.json({ message: "User deleted", id });
} catch (err) { } catch (err) {
logError(req, "users.deleteUser", err);
sendError(res, 500, "Failed to delete user"); sendError(res, 500, "Failed to delete user");
} }
}; };
@ -63,7 +66,7 @@ exports.getCurrentUser = async (req, res) => {
res.json(user); res.json(user);
} catch (err) { } catch (err) {
console.error("Error getting current user:", err); logError(req, "users.getCurrentUser", err);
sendError(res, 500, "Failed to get user profile"); sendError(res, 500, "Failed to get user profile");
} }
}; };
@ -89,7 +92,7 @@ exports.updateCurrentUser = async (req, res) => {
res.json({ message: "Profile updated successfully", user: updated }); res.json({ message: "Profile updated successfully", user: updated });
} catch (err) { } catch (err) {
console.error("Error updating user profile:", err); logError(req, "users.updateCurrentUser", err);
sendError(res, 500, "Failed to update profile"); sendError(res, 500, "Failed to update profile");
} }
}; };
@ -131,7 +134,7 @@ exports.changePassword = async (req, res) => {
res.json({ message: "Password changed successfully" }); res.json({ message: "Password changed successfully" });
} catch (err) { } catch (err) {
console.error("Error changing password:", err); logError(req, "users.changePassword", err);
sendError(res, 500, "Failed to change password"); sendError(res, 500, "Failed to change password");
} }
}; };

View File

@ -1,5 +1,6 @@
const householdModel = require("../models/household.model"); const householdModel = require("../models/household.model");
const { sendError } = require("../utils/http"); const { sendError } = require("../utils/http");
const { logError } = require("../utils/logger");
// Middleware to check if user belongs to household // Middleware to check if user belongs to household
exports.householdAccess = async (req, res, next) => { exports.householdAccess = async (req, res, next) => {
@ -29,7 +30,7 @@ exports.householdAccess = async (req, res, next) => {
next(); next();
} catch (error) { } catch (error) {
console.error("Household access check error:", error); logError(req, "middleware.householdAccess", error);
sendError(res, 500, "Server error checking household access"); sendError(res, 500, "Server error checking household access");
} }
}; };
@ -84,7 +85,7 @@ exports.storeAccess = async (req, res, next) => {
next(); next();
} catch (error) { } catch (error) {
console.error("Store access check error:", error); logError(req, "middleware.storeAccess", error);
sendError(res, 500, "Server error checking store access"); sendError(res, 500, "Server error checking store access");
} }
}; };

20
backend/utils/logger.js Normal file
View File

@ -0,0 +1,20 @@
const { safeErrorMessage } = require("./redaction");
function formatExtra(extra = {}) {
return Object.entries(extra)
.filter(([, value]) => value !== undefined && value !== null && value !== "")
.map(([key, value]) => `${key}=${String(value)}`)
.join(" ");
}
function logError(req, context, error, extra = {}) {
const requestId = req?.request_id || "unknown";
const message = safeErrorMessage(error);
const extraText = formatExtra(extra);
const suffix = extraText ? ` ${extraText}` : "";
console.error(`[${context}] request_id=${requestId} message=${message}${suffix}`);
}
module.exports = {
logError,
};