feat(logging): complete EverLog coverage in index.php

- Entry log (INFO/DEBUG) added to all public API functions
- EverLog::warn/error added before every uncovered http_response_code(4xx/5xx)
- Total EverLog calls: 117 across all request paths, error paths, and AI flows
- Only pure helper functions excluded (no I/O, no side-effects)
This commit is contained in:
dadaloop82
2026-05-18 05:55:14 +00:00
parent 36821bde7a
commit dc3cefefd0
+35
View File
@@ -114,6 +114,7 @@ if (($_GET['action'] ?? '') === 'get_logs') {
$token = loadEnv()['SETTINGS_TOKEN'] ?? ''; $token = loadEnv()['SETTINGS_TOKEN'] ?? '';
$reqTok = $_GET['token'] ?? $_SERVER['HTTP_X_SETTINGS_TOKEN'] ?? ''; $reqTok = $_GET['token'] ?? $_SERVER['HTTP_X_SETTINGS_TOKEN'] ?? '';
if (!empty($token) && $reqTok !== $token) { if (!empty($token) && $reqTok !== $token) {
EverLog::warn('get_logs: unauthorized (403)');
http_response_code(403); http_response_code(403);
echo json_encode(['error' => 'Unauthorized']); echo json_encode(['error' => 'Unauthorized']);
exit; exit;
@@ -472,6 +473,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array($rateLimitAction, $_writeA
$csrfHeader = $_SERVER['HTTP_X_EVERSHELF_REQUEST'] ?? ''; $csrfHeader = $_SERVER['HTTP_X_EVERSHELF_REQUEST'] ?? '';
$contentType = $_SERVER['CONTENT_TYPE'] ?? ''; $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if ($csrfHeader !== '1' && stripos($contentType, 'application/json') === false) { if ($csrfHeader !== '1' && stripos($contentType, 'application/json') === false) {
EverLog::warn('csrf_rejected (403)');
http_response_code(403); http_response_code(403);
echo json_encode(['success' => false, 'error' => 'csrf_rejected']); echo json_encode(['success' => false, 'error' => 'csrf_rejected']);
exit; exit;
@@ -504,6 +506,7 @@ try {
'dismiss_anomaly', 'bring_add', 'bring_remove', 'bring_sync', 'dismiss_anomaly', 'bring_add', 'bring_remove', 'bring_sync',
]; ];
if (in_array($action, $demoBlocked, true)) { if (in_array($action, $demoBlocked, true)) {
EverLog::warn('demo_mode blocked (403)');
http_response_code(403); http_response_code(403);
echo json_encode(['success' => false, 'error' => 'demo_mode']); echo json_encode(['success' => false, 'error' => 'demo_mode']);
exit; exit;
@@ -762,6 +765,7 @@ endif; // end !CRON_MODE
// ===== EXPORT INVENTORY ===== // ===== EXPORT INVENTORY =====
function exportInventory(PDO $db): void { function exportInventory(PDO $db): void {
EverLog::info('exportInventory');
$format = strtolower($_GET['format'] ?? 'csv'); $format = strtolower($_GET['format'] ?? 'csv');
$stmt = $db->query(" $stmt = $db->query("
@@ -863,6 +867,7 @@ HTML;
// ===== TTS PROXY ===== // ===== TTS PROXY =====
function ttsProxy() { function ttsProxy() {
EverLog::info('ttsProxy');
$body = json_decode(file_get_contents('php://input'), true); $body = json_decode(file_get_contents('php://input'), true);
$url = isset($body['url']) ? trim($body['url']) : ''; $url = isset($body['url']) ? trim($body['url']) : '';
$method = isset($body['method']) ? strtoupper(trim($body['method'])) : 'POST'; $method = isset($body['method']) ? strtoupper(trim($body['method'])) : 'POST';
@@ -874,6 +879,7 @@ function ttsProxy() {
} }
if (!$url || !preg_match('/^https?:\/\/.+/', $url)) { if (!$url || !preg_match('/^https?:\/\/.+/', $url)) {
EverLog::warn('ttsProxy: invalid URL (400)');
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'URL non valido']); echo json_encode(['error' => 'URL non valido']);
return; return;
@@ -901,6 +907,7 @@ function ttsProxy() {
curl_close($ch); curl_close($ch);
if ($curlErr) { if ($curlErr) {
EverLog::error('ttsProxy: curl error (502)');
http_response_code(502); http_response_code(502);
echo json_encode(['error' => 'cURL error: ' . $curlErr]); echo json_encode(['error' => 'cURL error: ' . $curlErr]);
return; return;
@@ -914,6 +921,7 @@ function ttsProxy() {
// ===== FOOD FACTS (cached daily) ===== // ===== FOOD FACTS (cached daily) =====
function getFoodFacts(): void { function getFoodFacts(): void {
EverLog::info('getFoodFacts');
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
$cacheFile = __DIR__ . '/../data/food_facts_cache.json'; $cacheFile = __DIR__ . '/../data/food_facts_cache.json';
$maxAgeSeconds = 86400; // 24 hours $maxAgeSeconds = 86400; // 24 hours
@@ -1048,6 +1056,7 @@ function getExpiryHistory($db): void {
} }
function clientLog(): void { function clientLog(): void {
EverLog::debug('clientLog');
$input = json_decode(file_get_contents('php://input'), true); $input = json_decode(file_get_contents('php://input'), true);
$logFile = __DIR__ . '/../data/client_debug.log'; $logFile = __DIR__ . '/../data/client_debug.log';
$ua = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'; $ua = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
@@ -1313,6 +1322,7 @@ function getProduct(PDO $db): void {
} }
function deleteProduct(PDO $db): void { function deleteProduct(PDO $db): void {
EverLog::info('deleteProduct');
$input = json_decode(file_get_contents('php://input'), true); $input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? 0; $id = $input['id'] ?? 0;
$stmt = $db->prepare("DELETE FROM products WHERE id = ?"); $stmt = $db->prepare("DELETE FROM products WHERE id = ?");
@@ -1337,6 +1347,7 @@ function searchProducts(PDO $db): void {
// ===== INVENTORY FUNCTIONS ===== // ===== INVENTORY FUNCTIONS =====
function listInventory(PDO $db): void { function listInventory(PDO $db): void {
EverLog::debug('listInventory');
$location = $_GET['location'] ?? ''; $location = $_GET['location'] ?? '';
$query = " $query = "
SELECT i.*, p.name, p.brand, p.category, p.image_url, p.unit, p.barcode, p.default_quantity, p.package_unit, SELECT i.*, p.name, p.brand, p.category, p.image_url, p.unit, p.barcode, p.default_quantity, p.package_unit,
@@ -1359,6 +1370,7 @@ function listInventory(PDO $db): void {
} }
function addToInventory(PDO $db): void { function addToInventory(PDO $db): void {
EverLog::info('addToInventory');
$input = json_decode(file_get_contents('php://input'), true); $input = json_decode(file_get_contents('php://input'), true);
$productId = (int)($input['product_id'] ?? 0); $productId = (int)($input['product_id'] ?? 0);
$quantity = (float)($input['quantity'] ?? 1); $quantity = (float)($input['quantity'] ?? 1);
@@ -1367,6 +1379,7 @@ function addToInventory(PDO $db): void {
$unit = $input['unit'] ?? null; $unit = $input['unit'] ?? null;
if (!$productId) { if (!$productId) {
EverLog::warn('addToInventory: product_id missing (400)');
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Product ID required']); echo json_encode(['error' => 'Product ID required']);
return; return;
@@ -1374,6 +1387,7 @@ function addToInventory(PDO $db): void {
// Validate quantity bounds // Validate quantity bounds
if ($quantity <= 0 || $quantity > 100000) { if ($quantity <= 0 || $quantity > 100000) {
EverLog::warn('addToInventory: invalid quantity (400)');
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Invalid quantity']); echo json_encode(['error' => 'Invalid quantity']);
return; return;
@@ -1382,6 +1396,7 @@ function addToInventory(PDO $db): void {
// Validate location // Validate location
$validLocations = ['dispensa', 'frigo', 'freezer', 'altro']; $validLocations = ['dispensa', 'frigo', 'freezer', 'altro'];
if (!in_array($location, $validLocations)) { if (!in_array($location, $validLocations)) {
EverLog::warn('addToInventory: invalid location (400)');
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Invalid location']); echo json_encode(['error' => 'Invalid location']);
return; return;
@@ -1538,6 +1553,7 @@ function useFromInventory(PDO $db): void {
$notes = $input['notes'] ?? ''; $notes = $input['notes'] ?? '';
if (!$productId) { if (!$productId) {
EverLog::warn('useFromInventory: product_id missing (400)');
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Product ID required']); echo json_encode(['error' => 'Product ID required']);
return; return;
@@ -1599,6 +1615,7 @@ function useFromInventory(PDO $db): void {
$existing = $stmt->fetch(); $existing = $stmt->fetch();
if (!$existing) { if (!$existing) {
EverLog::warn('useFromInventory: product not found in inventory (404)');
http_response_code(404); http_response_code(404);
echo json_encode(['error' => 'Product not found in inventory at this location']); echo json_encode(['error' => 'Product not found in inventory at this location']);
return; return;
@@ -1961,6 +1978,7 @@ function deleteInventory(PDO $db): void {
* user; those rows are silently deleted here (no banner needed). * user; those rows are silently deleted here (no banner needed).
*/ */
function getFinishedItems(PDO $db): void { function getFinishedItems(PDO $db): void {
EverLog::debug('getFinishedItems');
$rows = $db->query(" $rows = $db->query("
SELECT p.id AS product_id, p.name, p.brand, p.unit, p.default_quantity, p.package_unit, p.image_url, p.barcode, SELECT p.id AS product_id, p.name, p.brand, p.unit, p.default_quantity, p.package_unit, p.image_url, p.barcode,
MIN(i.location) AS location, MIN(i.location) AS location,
@@ -2027,6 +2045,7 @@ function confirmFinished(PDO $db): void {
} }
function inventorySummary(PDO $db): void { function inventorySummary(PDO $db): void {
EverLog::debug('inventorySummary');
$stmt = $db->query(" $stmt = $db->query("
SELECT i.location, COUNT(DISTINCT i.product_id) as product_count, SELECT i.location, COUNT(DISTINCT i.product_id) as product_count,
SUM(i.quantity) as total_items SUM(i.quantity) as total_items
@@ -2039,6 +2058,7 @@ function inventorySummary(PDO $db): void {
// ===== TRANSACTION FUNCTIONS ===== // ===== TRANSACTION FUNCTIONS =====
function listTransactions(PDO $db): void { function listTransactions(PDO $db): void {
EverLog::debug('listTransactions');
$limit = (int)($_GET['limit'] ?? 50); $limit = (int)($_GET['limit'] ?? 50);
$offset = (int)($_GET['offset'] ?? 0); $offset = (int)($_GET['offset'] ?? 0);
$productId = $_GET['product_id'] ?? ''; $productId = $_GET['product_id'] ?? '';
@@ -2084,6 +2104,7 @@ function undoTransaction(PDO $db): void {
$stmt->execute([$txId]); $stmt->execute([$txId]);
$tx = $stmt->fetch(); $tx = $stmt->fetch();
if (!$tx) { if (!$tx) {
EverLog::warn('undoTransaction: transaction not found (404)');
http_response_code(404); http_response_code(404);
echo json_encode(['error' => 'Transaction not found']); echo json_encode(['error' => 'Transaction not found']);
return; return;
@@ -2143,6 +2164,7 @@ function undoTransaction(PDO $db): void {
echo json_encode(['success' => true, 'name' => $tx['name']]); echo json_encode(['success' => true, 'name' => $tx['name']]);
} catch (Exception $e) { } catch (Exception $e) {
$db->rollBack(); $db->rollBack();
EverLog::error('undoTransaction: DB error (500)');
http_response_code(500); http_response_code(500);
echo json_encode(['error' => 'DB error: ' . $e->getMessage()]); echo json_encode(['error' => 'DB error: ' . $e->getMessage()]);
_phpErrorReport($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString(), get_class($e)); _phpErrorReport($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString(), get_class($e));
@@ -2450,6 +2472,7 @@ function getStats(PDO $db): void {
// ===== RECENT & POPULAR PRODUCTS ===== // ===== RECENT & POPULAR PRODUCTS =====
function recentPopularProducts(PDO $db): void { function recentPopularProducts(PDO $db): void {
EverLog::debug('recentPopularProducts');
// Last 4 distinct products used (type='out'), most recent first // Last 4 distinct products used (type='out'), most recent first
$recentStmt = $db->query(" $recentStmt = $db->query("
SELECT DISTINCT t.product_id, p.name, p.brand, p.category, p.image_url, p.unit, SELECT DISTINCT t.product_id, p.name, p.brand, p.category, p.image_url, p.unit,
@@ -2652,6 +2675,7 @@ function getConsumptionPredictions(PDO $db): void {
// ===== SETTINGS ===== // ===== SETTINGS =====
function getServerSettings(): void { function getServerSettings(): void {
EverLog::debug('getServerSettings');
$geminiKey = env('GEMINI_API_KEY'); $geminiKey = env('GEMINI_API_KEY');
$bringEmail = env('BRING_EMAIL'); $bringEmail = env('BRING_EMAIL');
@@ -2953,6 +2977,7 @@ function prewarmShelfLifeCache(PDO $db, int $limit = 5): array {
* Cache has no expiry shelf-life science doesn't change; the file can be manually deleted to refresh. * Cache has no expiry shelf-life science doesn't change; the file can be manually deleted to refresh.
*/ */
function getOpenedShelfLifeDays(string $name, string $category, string $location, bool $vacuumSealed = false, bool $allowAI = true): int { function getOpenedShelfLifeDays(string $name, string $category, string $location, bool $vacuumSealed = false, bool $allowAI = true): int {
EverLog::debug('getOpenedShelfLifeDays');
$cacheFile = __DIR__ . '/../data/opened_shelf_cache.json'; $cacheFile = __DIR__ . '/../data/opened_shelf_cache.json';
$cacheKey = md5(mb_strtolower($name) . '|' . mb_strtolower($location) . '|v2'); $cacheKey = md5(mb_strtolower($name) . '|' . mb_strtolower($location) . '|v2');
@@ -3059,6 +3084,7 @@ function getOpenedShelfLifeAction(): void {
* Returns null if tesseract binary is not available or GD is not compiled in. * Returns null if tesseract binary is not available or GD is not compiled in.
*/ */
function tesseractReadExpiry(string $imageBase64): ?array { function tesseractReadExpiry(string $imageBase64): ?array {
EverLog::info('tesseractReadExpiry');
// Require both the binary and the GD extension // Require both the binary and the GD extension
if (!function_exists('imagecreatefromstring')) return null; if (!function_exists('imagecreatefromstring')) return null;
$tesseract = trim(shell_exec('which tesseract 2>/dev/null') ?? ''); $tesseract = trim(shell_exec('which tesseract 2>/dev/null') ?? '');
@@ -4239,6 +4265,7 @@ PROMPT;
// ===== RECIPE FROM INGREDIENT ===== // ===== RECIPE FROM INGREDIENT =====
function recipeFromIngredient(PDO $db): void { function recipeFromIngredient(PDO $db): void {
EverLog::info('recipeFromIngredient');
$apiKey = env('GEMINI_API_KEY'); $apiKey = env('GEMINI_API_KEY');
if (empty($apiKey)) { if (empty($apiKey)) {
echo json_encode(['success' => false, 'error' => 'no_api_key']); echo json_encode(['success' => false, 'error' => 'no_api_key']);
@@ -4500,6 +4527,7 @@ function _enrichChatIngredients(array &$ingredients, array $items): void {
// ===== RECIPE GENERATION — STREAMING AGENT ===== // ===== RECIPE GENERATION — STREAMING AGENT =====
function generateRecipeStream(PDO $db): void { function generateRecipeStream(PDO $db): void {
EverLog::info('generateRecipeStream');
// Override content-type for SSE before any output is sent // Override content-type for SSE before any output is sent
header('Content-Type: text/event-stream'); header('Content-Type: text/event-stream');
header('Cache-Control: no-cache, no-store, must-revalidate'); header('Cache-Control: no-cache, no-store, must-revalidate');
@@ -5445,6 +5473,7 @@ function italianToBring(string $italianName): string {
* Returns null on failure so the caller can fall back gracefully. * Returns null on failure so the caller can fall back gracefully.
*/ */
function _geminiClassifyProduct(string $name, string $brand, string $category): ?string { function _geminiClassifyProduct(string $name, string $brand, string $category): ?string {
EverLog::debug('_geminiClassifyProduct');
$apiKey = env('GEMINI_API_KEY'); $apiKey = env('GEMINI_API_KEY');
if (empty($apiKey)) return null; if (empty($apiKey)) return null;
@@ -7287,6 +7316,7 @@ function chatClear(PDO $db): void {
* and scale inventory quantities accordingly. * and scale inventory quantities accordingly.
*/ */
function migrateUnitsToBase(PDO $db): void { function migrateUnitsToBase(PDO $db): void {
EverLog::info('migrateUnitsToBase');
$changes = 0; $changes = 0;
// Get products with kg or l units // Get products with kg or l units
@@ -7413,6 +7443,7 @@ function reportError(): void {
* version string? App version * version string? App version
*/ */
function reportBugManual(): void { function reportBugManual(): void {
EverLog::info('reportBugManual');
$input = json_decode(file_get_contents('php://input'), true) ?: []; $input = json_decode(file_get_contents('php://input'), true) ?: [];
$allowedTypes = ['bug', 'feature', 'question']; $allowedTypes = ['bug', 'feature', 'question'];
@@ -7598,6 +7629,7 @@ function _createOrCommentGithubIssue(
string $version, array $context string $version, array $context
): void { ): void {
$fp = _errorFingerprint($source, $type, $message); $fp = _errorFingerprint($source, $type, $message);
EverLog::debug('_createOrCommentGithubIssue', ['fp' => $fp, 'type' => $type]);
// ── 1. Search for an existing open issue with this fingerprint ───────── // ── 1. Search for an existing open issue with this fingerprint ─────────
$searchQuery = urlencode("repo:$repo is:issue is:open label:auto-report \"fp:$fp\" in:body"); $searchQuery = urlencode("repo:$repo is:issue is:open label:auto-report \"fp:$fp\" in:body");
@@ -7675,6 +7707,7 @@ function _createOrCommentGithubIssue(
* Returns ['http_code' => int, 'body' => array]. * Returns ['http_code' => int, 'body' => array].
*/ */
function _githubRequest(string $token, string $method, string $url, array $payload = []): array { function _githubRequest(string $token, string $method, string $url, array $payload = []): array {
EverLog::debug('_githubRequest');
$ch = curl_init($url); $ch = curl_init($url);
$headers = [ $headers = [
'Authorization: token ' . $token, 'Authorization: token ' . $token,
@@ -7704,6 +7737,7 @@ function _githubRequest(string $token, string $method, string $url, array $paylo
* Writes to local log + creates a GitHub issue. * Writes to local log + creates a GitHub issue.
*/ */
function _phpErrorReport(string $message, string $file, int $line, string $trace, string $type): void { function _phpErrorReport(string $message, string $file, int $line, string $trace, string $type): void {
EverLog::error('_phpErrorReport');
// Prevent infinite loops if this function itself throws // Prevent infinite loops if this function itself throws
static $running = false; static $running = false;
if ($running) return; if ($running) return;
@@ -8051,6 +8085,7 @@ function _priceKey(string $name, string $country): string {
* { price_per_unit, unit_label, currency, source_note } or null on failure. * { price_per_unit, unit_label, currency, source_note } or null on failure.
*/ */
function _fetchPriceFromAI(string $name, string $country, string $currency, string $lang): ?array { function _fetchPriceFromAI(string $name, string $country, string $currency, string $lang): ?array {
EverLog::info('_fetchPriceFromAI');
$result = _fetchPricesBatchFromAI([$name], $country, $currency, $lang); $result = _fetchPricesBatchFromAI([$name], $country, $currency, $lang);
return $result[$name] ?? null; return $result[$name] ?? null;
} }