getgrav/grav · error · RuntimeException
JSON encoding failed: %s. Encoding: %s
Error message
JSON encoding failed: %s. Encoding: %s
What it means
Grav ships a compat copy of Monolog's Utils (system/src/Grav/Framework/Compat/Monolog/Utils.php:163) whose JSON encoder throws a RuntimeException embedding the json_last_error message (e.g. 'Malformed UTF-8 characters') and a var_export of the offending data whenever json_encode() fails. It fires while a log record is normalized/encoded, so the culprit is data you tried to log, not the logger config.
Source
Thrown at system/src/Grav/Framework/Compat/Monolog/Utils.php:163
{
switch ($code) {
case JSON_ERROR_DEPTH:
$msg = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Unexpected control character found';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$msg = 'Unknown error';
}
throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
}
/**
* @param mixed $data
*/
public static function detectAndCleanUtf8(&$data)
{
if (is_string($data) && !preg_match('//u', $data)) {
$data = preg_replace_callback(
'/[\x80-\xFF]+/',
static function ($m) { return utf8_encode($m[0]); },
$data
);
$data = str_replace(
['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'],
['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'],
$data
);View on GitHub (pinned to 6040efed04)
Solutions
- Sanitize before logging: mb_convert_encoding($value, 'UTF-8', 'UTF-8') strips/replaces invalid sequences; Grav's compat Utils::detectAndCleanUtf8() exists for the same purpose.
- Do not log raw binary or unknown-encoding payloads — log identifiers, lengths, or hex dumps instead.
- Keep Grav/Monolog current so encoding failures are handled without aborting the log call.
Example fix
// before
$grav['log']->error('Payload: ' . $rawBody); // invalid UTF-8 -> RuntimeException
// after
$clean = mb_convert_encoding($rawBody, 'UTF-8', 'UTF-8');
$grav['log']->error('Payload: ' . $clean); Defensive patterns
Strategy: fallback
Validate before calling
$clean = array_map(
static fn($v) => \is_string($v) ? mb_convert_encoding($v, 'UTF-8', 'UTF-8') : $v,
$context
);
$grav['log']->error('Context', $clean); Type guard
function isUtf8(string $value): bool
{
return (bool) preg_match('//u', $value);
} Try / catch
try {
$grav['log']->error($message, $context);
} catch (\RuntimeException $e) {
if (str_starts_with($e->getMessage(), 'JSON encoding failed')) {
$grav['log']->error(mb_convert_encoding($message, 'UTF-8', 'UTF-8'));
}
} Prevention
- Sanitize external data with mb_convert_encoding($s, 'UTF-8', 'UTF-8') before logging.
- Never log raw binary bodies; log IDs, sizes, or bin2hex() snippets.
- Avoid substr() on multibyte strings before logging — use mb_substr().
When it happens
Trigger: Logging a record containing invalid UTF-8 — raw binary from a database or uploaded file, strings cropped mid-multibyte-sequence by substr(), or legacy encodings (ISO-8859-1/Windows-1252) never converted; debug handlers serializing objects with binary/resource properties.
Common situations: Production sites logging third-party API responses in legacy encodings; logging raw request bodies that include binary uploads; data corrupted by naive string truncation before logging.
Related errors
- Encoding JSON failed: {json_last_error_msg}
- Decoding markdown failed
- json_encode(): failed to encode group permissions
- json_encode(): failed to encode dependencies
- Decoding JSON failed: {json_last_error_msg}
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/fe029ead953c8f6f.
Report an issue: GitHub.