Summary
OtelInstrumentation\Log\Formatter\ContextJsonFormatter::format() calls json_encode() with JSON_THROW_ON_ERROR unconditionally:
$json = json_encode($fixed + $context, JSON_THROW_ON_ERROR | $this->_config['flags']);
If the log message or any context value contains invalid UTF-8 byte sequences (e.g. Shift-JIS leftovers, binary data accidentally logged), json_encode() fails and this throws a JsonException.
Why this is a problem
In CakePHP, Cake\Error\ExceptionTrap::handleException() calls the logger to record an unhandled exception, but that call is not wrapped in its own try/catch. If the act of logging an exception itself throws (because the exception message/context contains invalid UTF-8), the JsonException propagates out of the exception handler and crashes the whole application (unresponsive, no 500 response returned) instead of just failing to log.
A logging failure should never be allowed to take down the whole request.
Suggested fix
Catch JsonException inside format() and fall back to a safe, always-encodable payload, e.g.:
public function format(mixed $level, string $message, array $context = []): string
{
$fixed = ['level' => (string)$level, 'message' => $message];
if ($this->_config['dateFormat'] !== null) {
$fixed = ['date' => date($this->_config['dateFormat'])] + $fixed;
}
try {
$json = json_encode($fixed + $context, JSON_THROW_ON_ERROR | $this->_config['flags']);
} catch (JsonException) {
$json = json_encode(
['level' => (string)$level, 'message' => '[unloggable message: contains invalid UTF-8]'],
JSON_UNESCAPED_UNICODE
);
}
return $this->_config['appendNewline'] ? $json . "\n" : $json;
}
Alternatively, using JSON_INVALID_UTF8_SUBSTITUTE (without JSON_THROW_ON_ERROR) would sanitize instead of dropping the message, which may be preferable to avoid losing log content entirely — happy to discuss which approach fits this plugin's intended behavior better.
Context
This was discovered while investigating a crash risk in our CakePHP application (a downstream consumer of this plugin). We worked around it downstream with a wrapper formatter (SafeContextJsonFormatter) that catches JsonException around parent::format(), but fixing it here would remove the need for every consumer to duplicate that workaround.
Reference: j-com-dev/billing-service#164 (a related but distinct fix for the same class of issue in CakePHP core's BaseLog::interpolate(), which is unaffected by any change here since it's outside this plugin).
Summary
OtelInstrumentation\Log\Formatter\ContextJsonFormatter::format()callsjson_encode()withJSON_THROW_ON_ERRORunconditionally:If the log message or any context value contains invalid UTF-8 byte sequences (e.g. Shift-JIS leftovers, binary data accidentally logged),
json_encode()fails and this throws aJsonException.Why this is a problem
In CakePHP,
Cake\Error\ExceptionTrap::handleException()calls the logger to record an unhandled exception, but that call is not wrapped in its own try/catch. If the act of logging an exception itself throws (because the exception message/context contains invalid UTF-8), theJsonExceptionpropagates out of the exception handler and crashes the whole application (unresponsive, no 500 response returned) instead of just failing to log.A logging failure should never be allowed to take down the whole request.
Suggested fix
Catch
JsonExceptioninsideformat()and fall back to a safe, always-encodable payload, e.g.:Alternatively, using
JSON_INVALID_UTF8_SUBSTITUTE(withoutJSON_THROW_ON_ERROR) would sanitize instead of dropping the message, which may be preferable to avoid losing log content entirely — happy to discuss which approach fits this plugin's intended behavior better.Context
This was discovered while investigating a crash risk in our CakePHP application (a downstream consumer of this plugin). We worked around it downstream with a wrapper formatter (
SafeContextJsonFormatter) that catchesJsonExceptionaroundparent::format(), but fixing it here would remove the need for every consumer to duplicate that workaround.Reference: j-com-dev/billing-service#164 (a related but distinct fix for the same class of issue in CakePHP core's
BaseLog::interpolate(), which is unaffected by any change here since it's outside this plugin).