Structured logs, and the field that must be a field

A formatted message is a string a machine has to parse back, and every parser is a regular expression somebody will break.

// a person can read it; nothing can query it
Log::info("Order {$order->id} failed for user {$user->id}: {$e->getMessage()}");

// queryable
Log::warning('order.sync.failed', [
    'order_id'  => $order->id,
    'user_id'   => $user->id,
    'trace_id'  => $trace->id(),
    'exception' => $e->getMessage(),
    'attempt'   => $attempt,
]);

The message becomes a stable event name rather than a sentence, which is what makes counting occurrences possible — a message with an interpolated id is a unique string and cannot be grouped. Everything variable moves into fields. The cost is a log that is harder to read on a terminal, which a formatter for local development solves and which is a real objection worth answering rather than dismissing.