Josh Davies

Senior Software Engineer

I build beautiful, engaging, and perfectly executed frontend and backend experiences. While finding time to write, hike and read.

Blog

A Log Line Nobody Can Search Is a Diary

  • logging
  • debugging
  • laravel
  • typescript

Something breaks in production. You open the logs. You find this:

Something went wrong processing order

Which order? Which user? Which step? Nobody knows. That line is not a log. It's a diary entry.

Logs are for searching, not reading

Most of the time you don't read logs top to bottom. You search them. You filter by an order ID, a user, a job name, a request.

If the useful details are buried in a sentence, or missing, search can't help you. So write log lines for the person grepping at 2am, not for the person writing the code.

Log an event, attach context

Keep the message short and stable. Put the details in context.

In Laravel, that context is the second argument:

Log::warning('order.payment_failed', [
    'order_id' => $order->id,
    'user_id' => $order->user_id,
    'gateway' => $gateway->name(),
    'attempt' => $attempt,
]);

The message never changes, so you can search for every payment failure at once. The context tells you which one you're looking at.

Set context once, not on every line

If every log line in a request needs the same request ID, don't pass it by hand everywhere. Laravel lets you share context for the rest of the request:

Log::withContext([
    'request_id' => $request->header('X-Request-Id') ?? (string) Str::uuid(),
]);

Put that in a middleware and every log line after it carries the ID. One search and you have the whole story of one request.

Same idea in Node

Here is the same idea as a small helper in TypeScript that writes JSON lines:

type Context = Record<string, unknown>;
 
export function logEvent(event: string, context: Context = {}) {
  console.log(JSON.stringify({
    event,
    ...context,
    at: new Date().toISOString(),
  }));
}
 
logEvent('webhook.rejected', { provider: 'stripe', reason: 'bad_signature' });

One JSON object per line is easy for basically any log tool to parse. A pretty sentence is not.

Things that don't belong in logs

Context is good. Too much context is a security problem.

  • Passwords, tokens, and API keys. Ever.
  • Full card numbers or other payment data.
  • Whole request bodies "just in case".

Log the ID of the thing, not the thing.

A quick gut check

Before you ship a log line, ask:

  1. Could I search for this event by name?
  2. Does it tell me which record, user, or request it belongs to?
  3. Would I be fine with this line showing up in a screenshot?

If the answer to any of those is no, fix the line now. You won't have time to fix it during the outage.

← All posts