Laravel / Advanced / Lesson 17
Observability and failure handling
Connect logs, metrics, and failure contracts to user-visible outcomes while protecting sensitive context and distinguishing liveness from dependency health.
On this page
Lesson goals
- Choose structured events and bounded metric dimensions.
- Correlate requests and queued work without logging secrets.
- Test reporting and recovery independently from message formatting.
Observe the operation the user cares about
A useful signal describes an outcome such as report completed, report rejected, or report failed, not merely that a controller method ran. The synthetic fixture has three completed request observations with duration and status, plus a report failure identified by a server-generated correlation key. Decide the operation boundary, success definition, and acceptable delay before choosing dashboards. Logs help inspect individual events, metrics summarize bounded populations, and traces can connect stages. None is a substitute for a clear error contract. A system can answer HTTP successfully while its queued work is failing indefinitely.
Use a correct denominator and a declared population
The complete PHP model counts failures among the three synthetic request observations. It prints counts and a fraction rather than inventing a service-level objective or measured latency percentile. A real error-rate metric must define which statuses or domain outcomes count as failures, its time window, and its denominator. An empty population is not automatically a zero-error proof. Percentiles require an appropriate distribution, not averaging per-request percentile labels. Keep metric labels bounded, such as operation and outcome; request identifiers and user identifiers belong in controlled event context, not unbounded label sets.
<?php
declare(strict_types=1);
$observations = [
["status" => 200, "duration_ms" => 30],
["status" => 500, "duration_ms" => 120],
["status" => 200, "duration_ms" => 50],
];
$failures = count(array_filter($observations, fn (array $event): bool => $event["status"] >= 500));
$total = count($observations);
echo "requests=", $total, " failures=", $failures, PHP_EOL;
echo "error_fraction=", $failures, "/", $total, PHP_EOL;
requests=3 failures=1
error_fraction=1/3
Exactly one of three observations has a server-error status. This is a synthetic arithmetic result, not a measured production error rate.
Attach useful identity without copying sensitive payloads
Laravel logging accepts structured context alongside a stable event message. The contextual fragment assumes report identifier 17, a server-generated correlation identifier, and a known release label. It deliberately excludes request bodies, tokens, uploaded bytes, and raw database bindings. Correlation can connect an accepted request to a queued operation when the identifier is carried explicitly. Validate externally supplied identifiers before using them in logs and avoid trusting them as security evidence. In long-running workers, lifecycle-scoped context must not leak into the next job. Passing event-local context is easier to reason about than retaining mutable global context accidentally.
<?php
use Illuminate\Support\Facades\Log;
Log::warning("report_generation_failed", [
"report_id" => 17,
"correlation_id" => $serverCorrelationId,
"release" => $releaseId,
"failure_category" => "source_unavailable",
]);
Expected isolated-log observation: one stable event with the selected fields and no secret payload. Actual channel formatting, retention, and delivery depend on logging configuration and are not executed here.
Separate reporting from rendering and recovery
Laravel exception reporting, HTTP rendering, and business recovery are different decisions. Report an unexpected failure at a meaningful boundary, return a safe response, and avoid logging the same exception at every layer before rethrowing. Expected validation or authorization failures should follow their established response contracts instead of becoming indistinguishable server incidents. Suppressing a report does not make the operation successful, and rendering a friendly message does not repair partial state. Keep APP_DEBUG disabled for public production responses. A health endpoint that confirms application boot does not automatically verify the database, cache, queue, or downstream service; additional checks need a deliberate bounded policy.
Test whether failure evidence remains useful
Use isolated log spies or fake channels to assert event category, selected context, and redaction rules. Test a queued failure, retry exhaustion, timeout, and logging-backend outage separately from the success path. An alert threshold needs baseline traffic and a user-impact objective; a universal percentage copied into every application is not a justified policy. Monitor queue delay and completion outcomes as well as HTTP status. Limit retention and access to operational data, and keep identifiers stable enough for diagnosis without exposing secrets. Verification should state exactly what was tested: a log-call assertion does not prove transport delivery, trace completeness, or recovery of the failed business operation.
Common errors and fixes
A retry storm is hidden behind successful HTTP acceptance.
- Why it happens
- Monitoring ends before asynchronous completion.
- How to fix it
- Track queued outcomes, delay, and exhaustion separately.
- Prevent it next time
- Define success at the actual business boundary.
Logs contain tokens or full request bodies.
- Why it happens
- Convenient context collection ignored data sensitivity.
- How to fix it
- Select safe structured fields and apply retention controls.
- Prevent it next time
- Test redaction and access policy.
Metrics become expensive because every request is a label.
- Why it happens
- Unbounded identifiers were used as metric dimensions.
- How to fix it
- Keep bounded labels and move detailed identity to controlled events.
- Prevent it next time
- Review label cardinality before instrumentation ships.
Check your understanding
1. Add a fourth observation with status 503 to the PHP fixture. Give the new counts and explain why no latency percentile can be inferred from those counts.
Show worked solution
There are four requests and two failures, so the printed fraction becomes 2/4. Those counters contain no duration distribution and cannot determine a latency percentile. Even the original listed durations would need an explicitly chosen percentile definition and sample policy. Keep error proportion and latency as separate measured signals with their own populations and windows.
2. A team adds correlation_id and user_id as labels on every latency metric. Propose a safer observation design while preserving per-request diagnosis.
Show worked solution
Use bounded metric dimensions such as route category, operation, and outcome. Store a validated correlation identifier in structured logs or traces with restricted access, and include user identity only when justified and appropriately protected. Link the aggregate alert to event investigation rather than creating one time series per request. Test both useful context and absence of sensitive payload fields.
Key takeaways
- Observe business completion, not only request acceptance.
- Use selected structured context and bounded metrics.
- Reporting evidence does not prove recovery or dependency health.