Observability Across Cloud Infrastructure and Operational Boundaries
Extend logging/metrics/tracing fundamentals to a distributed, multi-service cloud deployment by correlating telemetry across service boundaries with distributed tracing, and recognize what you can and cannot observe or control at each layer of a managed cloud stack.
This course's earlier Observability module already covers the fundamentals — structured logging, metrics, and the basics of tracing a single application. Those fundamentals don't change once your PHP application is deployed across cloud infrastructure, but two new problems appear that a single-instance, single-service mental model doesn't prepare you for: correlating one user's request across every service and infrastructure hop it actually touches, and knowing the real boundary of what you can see and control at each layer of a managed cloud stack you don't fully own. This short lesson covers exactly those two things, building directly on the Observability lesson's fundamentals rather than repeating them.
Correlating Telemetry Across Service Boundaries
A single user action in a cloud-deployed application routinely touches a load balancer, one of several stateless application server instances behind it, a message queue, one or more background worker instances, a managed database, and possibly a separate microservice or two — each of these is a separate process, often on separate infrastructure, each writing its own logs and emitting its own metrics independently. If a request is slow or fails, the Observability lesson's per-service logging and metrics tell you what happened inside any ONE of those components, but by themselves they don't tell you how to find the right log line in the load balancer's logs, the right log line in application instance #7, and the right log line in the queue worker that eventually processed it — all for the same single user request.
Distributed tracing solves exactly this correlation problem: a unique trace ID is generated at the very first point a request enters the system (typically at the load balancer or the first application service) and is then propagated forward through every subsequent hop — passed along in an HTTP header to the next service, embedded in the message payload placed on the queue, attached to the job a worker eventually picks up — so that every log line and every span emitted anywhere in that request's journey can be tagged with the same trace ID. Tools like OpenTelemetry (increasingly the standard, vendor-neutral instrumentation layer, with a PHP SDK) or managed tracing backends (AWS X-Ray, Google Cloud Trace, Datadog APM, Jaeger) then let you pull up one trace ID and see a full timeline of every hop that request took, across every service, in one view — turning 'grep five different services' logs and manually correlate timestamps' into 'look up one trace ID'.
The practical takeaway for a PHP team is that this correlation has to be designed in deliberately: the trace ID has to actually be generated, propagated in outgoing requests and queue payloads, and included in every structured log line, at every service boundary, or the chain breaks at whichever hop forgot to pass it along — an incomplete trace that stops at the queue is only marginally more useful than no tracing at all for that leg of the journey.
• A single request can touch a load balancer, several app instances, a queue, workers, and a database — each logging independently. • A trace ID generated at request entry and propagated through every hop (HTTP headers, queue payloads) is what lets telemetry be correlated across all of them. • OpenTelemetry (vendor-neutral, has a PHP SDK) or a managed tracing backend (X-Ray, Cloud Trace, Datadog APM, Jaeger) turn a trace ID into one unified timeline across services. • The chain only works end-to-end if every service boundary deliberately propagates and logs the trace ID — one forgotten hop breaks the correlation for that leg.
Operational Boundaries in a Managed Cloud Stack
Running on managed cloud infrastructure — a managed load balancer, a managed database service, a managed queue — trades operational burden for reduced visibility and control, and knowing exactly where that boundary sits at each layer is essential for correctly scoping an incident investigation. You can typically see, in full detail, everything happening inside your own application: your structured logs, your application-level metrics, your traces. What you often CANNOT see, at least not without the provider explicitly exposing it, is the internal behavior of the managed components sitting between and around your application — a managed load balancer's internal request queueing behavior before it ever reaches your instances, or a managed relational database's internal replication lag between its primary and its read replicas, beyond whatever summary metric the provider chooses to surface on a dashboard.
This matters concretely during an incident: if requests are slow and your own application's traces show your code executing quickly, the bottleneck may be sitting in a layer you don't have direct visibility into — a load balancer queueing requests under a traffic spike before handing them to your instances, or a managed database's replication falling behind in a way that only shows up as your application's read-replica queries returning stale-looking data, not as an error your code can catch. The operational skill here is recognizing this boundary honestly rather than assuming your own service's clean traces mean nothing is wrong anywhere — and knowing which provider-exposed metrics (load balancer request-queue depth, RDS replication-lag metrics, managed-queue backlog depth) exist specifically to give you a window past that boundary, even without full internal visibility.
Practically, this means treating provider-exposed operational metrics (CloudWatch/Cloud Monitoring dashboards for your load balancer and managed database, queue depth/age metrics from your managed queue service) as a required part of your observability setup alongside your own application's logs/metrics/traces — not an afterthought — precisely because they cover the parts of the request's journey that live outside your own code's visibility.
• You fully see your own application's logs, metrics, and traces; managed infrastructure often exposes only summary metrics, not internal behavior. • Example blind spots: a managed load balancer's internal request-queueing before reaching your instances; a managed database's internal replication lag beyond a summary metric. • A clean trace inside your own application does not rule out a bottleneck in a managed layer you can't see directly. • Provider-exposed operational metrics (load balancer queue depth, DB replication lag, queue backlog age) fill part of this blind spot and belong in your observability setup, not as an afterthought.
Code Examples
The trace ID is read from an incoming header if present (meaning an upstream service already started the trace) or generated fresh, then explicitly forwarded both to a downstream HTTP call's headers and into a queue job's payload — without this deliberate propagation, the worker's own logs would have no way to be correlated back to this specific checkout request.
What specific problem does a propagated trace ID solve that per-service logging and metrics (covered in the Observability lesson) do not solve by themselves? A. It replaces the need for structured logging entirely B. It correlates every log line and span across every service and infrastructure hop a single request touched, into one unified timeline C. It makes individual application logs unnecessary D. It only works for database queries, not HTTP calls
B. It correlates every log line and span across every service and infrastructure hop a single request touched, into one unified timeline — Per-service logs and metrics show what happened inside one component; a trace ID propagated through every hop (HTTP headers, queue payloads) is what lets you find and connect the relevant log lines and spans across every service a single request touched.
Why might your own application's clean, fast traces fail to explain why users are experiencing slow requests? A. Traces are never accurate B. The bottleneck may be occurring in a managed infrastructure layer (e.g. a load balancer's internal queueing, or database replication lag) that sits outside your own application's visibility C. This can never happen if tracing is set up correctly D. PHP cannot generate accurate traces
B. The bottleneck may be occurring in a managed infrastructure layer (e.g. a load balancer's internal queueing, or database replication lag) that sits outside your own application's visibility — A managed cloud stack has real operational boundaries — you typically can't see a load balancer's internal queueing or a managed database's internal replication behavior beyond whatever summary metrics the provider exposes, so a bottleneck can exist entirely outside what your own application's traces can show.
<?php
declare(strict_types=1);
// Read (or generate, if this service is the entry point) the trace ID for
// this request — see the Observability lesson for the base logging setup
// this builds on.
$traceId = $_SERVER['HTTP_X_TRACE_ID'] ?? bin2hex(random_bytes(16));
function logWithTrace(string $traceId, string $message, array $context = []): void
{
error_log(json_encode(['trace_id' => $traceId, 'message' => $message, ...$context]));
}
logWithTrace($traceId, 'Received checkout request');
// Propagate the trace ID to a downstream service call...
$ch = curl_init('https://inventory-service.internal/reserve');
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Trace-Id: {$traceId}"]);
curl_exec($ch);
// ...and into a queued job's payload, so the worker (a separate process,
// possibly on a separate machine) can log under the SAME trace ID.
$queue->push('SendOrderConfirmation', [
'order_id' => 555,
'trace_id' => $traceId,
]);