json_encode returns false long before you check json_last_error

json_encode() is all-or-nothing. One byte of invalid UTF-8 anywhere in the structure and the return value is false — not a partial document, not the offending field omitted, the whole thing. Since echo false prints an empty string, the endpoint answers 200 with a body of zero length and nothing in the log.

// one field carrying latin-1 bytes out of a column that predates utf8
$row = array('sku' => 'FR-100', 'name' => "FenxE9r frame");

$json = json_encode($row);

var_dump($json);            // bool(false)
echo json_last_error();     // 5
echo json_last_error_msg(); // Malformed UTF-8 characters, possibly incorrectly encoded

// so this is a 200 with an empty body, every time
header('Content-Type: application/json');
echo json_encode($row);

json_last_error_msg() arrived in 5.5 and is the difference between logging 5 and logging a sentence, which matters because the numeric codes are not memorable and the failure is rare enough that nobody will remember by the time it happens again. JSON_PARTIAL_OUTPUT_ON_ERROR, also 5.5, substitutes null for the value it could not encode and returns the rest — correct for a diagnostic log line, wrong for anything a client will act on, because it silently ships a document with a hole in it. The real fix is upstream: the bad bytes came from a column whose contents are not the encoding the connection claims, and mb_check_encoding() at the boundary finds them while there is still context to identify the row.