Lookout
000 015 030 045 060 075 090 105 120 135 150 165 180 195 210 225 240 255 270 285 300 315 330 345 360
4 min read Tom Shafer

Structured logging in Laravel: from dd() to searchable logs

Laravel structured logging done right: swap scattered dd() and Log::info for contextual logs you can actually search, filter, and alert on in production.

You shipped a fix, a customer says it is still broken, and your "logs" are a graveyard of dd() calls you forgot to delete and Log::info('here') lines that mean nothing a week later. When something breaks at 2 a.m., a flat wall of text is not a log — it is a dare. Laravel structured logging turns that wall into data: every line carries context you can search, filter, and alert on. This post walks through moving from debug-by-print to logs that actually answer "what happened, to whom, and when?"

Why dd() and bare strings fail you

dd() is a debugger, not a log. It stops execution, prints to the screen, and helps exactly one person — you, right now, in development. In production it is useless or dangerous.

Bare strings are only marginally better. A line like Log::info('Order failed') tells you something failed but not which order, which user, or why. Six months later, nobody can grep their way to an answer.

The fix is to log events with structure, not sentences.

Add context to every log line

Laravel's logger accepts a context array as its second argument. Use it. Instead of stuffing IDs into the message, attach them as fields:

Log::warning('Order payment failed', [
    'order_id' => $order->id,
    'user_id' => $order->user_id,
    'amount_cents' => $order->total_cents,
    'gateway' => 'stripe',
    'reason' => $exception->getMessage(),
]);

Now the message stays stable ("Order payment failed") while the fields vary. You can group by message, filter by gateway, and find every failure for one user without crafting a regex.

Share context across a request

Repeating the same fields on every call is tedious and easy to get wrong. Laravel lets you attach context once and have it ride along automatically:

Log::withContext([
    'request_id' => (string) Str::uuid(),
    'tenant_id' => $tenant->id,
]);

Set this early — middleware is a natural home — and every subsequent log line in that request inherits it. Suddenly you can pull up one request's entire story by a single id.

Pick levels that mean something

Levels are a filter, not decoration. A quick, consistent policy:

  • error / critical — something broke and a human may need to act
  • warning — recoverable, but worth watching (a retry, a fallback)
  • info — meaningful business events (an order placed, a plan changed)
  • debug — developer detail, usually off in production

When levels are consistent, "show me everything at warning and above for this tenant in the last hour" becomes a real query instead of a hope.

Structured logs only pay off if they leave the server. A log file on a box you have to SSH into is a structured log nobody reads. Send them somewhere central and searchable.

Lookout ingests logs over plain HTTP/JSON alongside your errors, so a log line and the exception it preceded live in the same place. Install is two commands:

composer require lookout/tracing
php artisan lookout:install

Set your connection in .env and you are done:

LOOKOUT_LARAVEL=true
LOOKOUT_API_KEY=your-project-api-key
LOOKOUT_URL=https://your-lookout-host.example.com

Because the fields are structured, you can filter by tenant_id, group by message, and set an alert when error-level logs spike over a 1h or 24h window — delivered to Email or Slack so the right person hears about it once, not a hundred times.

Start small, then expand

You do not need to refactor every log line today. Pick your three noisiest failure paths — payments, queued jobs, third-party calls — add real context to those, and route them somewhere searchable. The first time you resolve an incident by filtering on a field instead of scrolling, you will never go back to dd().

Lookout's free Starter tier gives you 10,000 events a month and a project, no credit card, which is plenty to wire up structured logging on a real app and feel the difference. Create your free project and turn your logs into answers.

laravel logging observability