A log line written as a sentence has to be parsed back into fields by a regular expression somebody maintains, and that expression breaks whenever the sentence changes. Writing JSON at the source removes the parsing step entirely.
// unparseable without a grok pattern, and the pattern is fragile
Log::info("Order 8841 shipped via UPS in 412ms");
// queryable, and the shape is the contract
Log::info('order.shipped', [
'order_id' => 8841,
'carrier' => 'UPS',
'duration_ms' => 412,
]);
The message becomes a stable event name rather than a sentence, which is what makes counting them possible. Types survive — duration_ms is a number in Elasticsearch and can be averaged, where a parsed string is not. The discipline that matters: once a field name is in the index it is effectively permanent, because a dashboard depends on it.