Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Metrics, Logs, and Traces Compared

By Kokil Thapa | Last reviewed: September 2026

Your checkout API returns 500 errors at 2 a.m. A dashboard shows a spike. A log line mentions a timeout. A trace pinpoints the slow database query. Those are three different observability signals, and Metrics, Logs, and Traces Compared is the map you need before you wire tooling. Metrics summarise system health over time. Logs record discrete events with context. Traces follow one request across services. This guide breaks down each signal, shows when to reach for which one, and walks through a stack that works on real Linux production servers running Laravel 12 or Symfony 8.1.

What is the difference between metrics, logs, and traces?

Think of observability as three lenses on the same running system. Each lens answers a different class of question. None replaces the others. A metric tells you that error rates jumped. A log tells you what exception fired. A trace tells you where in the call chain time was spent.

Metrics are aggregated numeric measurements sampled at intervals. Examples include request rate, p95 latency, queue depth, and MySQL connections in use. They are cheap to store and fast to query. You plot them on dashboards and attach alert thresholds.

Logs are immutable text or structured records emitted at event time. Each entry carries a timestamp, severity, message, and optional key-value fields. They excel at audit trails, authentication failures, payment callback payloads, and stack traces.

Traces capture causality. A trace groups spans — timed operations — for one logical request. Span A might be an HTTP controller. Span B might be an Eloquent query. Span C might be a Redis call. Together they reveal latency distribution inside a single user action.

Three Observability SignalsMetricsNumbers over timeCounters, gaugesHistogramsLogsEvents with contextErrors, auditsStructured JSONTracesRequest timelinesSpans across servicesLatency breakdownUnified Observability StackOpenTelemetry collector exports all threeCorrelate via trace_id in log fields
Metrics, logs, and traces compared — each signal type captures a different dimension of production system behaviour.

The observability versus monitoring distinction matters here. Monitoring watches known failure modes via predefined dashboards. Observability lets you ask new questions when something unexpected happens. Traces and structured logs unlock those ad-hoc questions. Metrics alone often cannot.

DimensionMetricsLogsTraces
Data shapeNumeric time-seriesText or JSON eventsTree of timed spans
Best questionIs the system healthy?What exactly happened?Where did time go?
Storage costLow (aggregated)High (verbose)Medium (sampled)
Cardinality riskHigh if labels explodeModerateModerate with sampling
Alert suitabilityExcellentPossible via log rulesGood for SLO burn
Retention typical30–90 days7–30 days hot, archive longer7–14 days
Example toolsPrometheus, GrafanaLoki, Graylog, ELKJaeger, Tempo, Zipkin

On booking platforms like Adventure Third Pole Trek, a metric might track confirmed bookings per minute. A log records a failed Khalti callback with the gateway response body. A trace shows that the payment verification span waited 4.2 seconds on an external API.

How do you collect metrics, logs, and traces in a production stack?

Collection architecture depends on team size and budget. A solo developer on a single Ubuntu 24 server can start simpler than a multi-service Kubernetes cluster. The principles stay the same: instrument at the source, ship centrally, correlate with shared identifiers.

Metrics collection with Prometheus

Prometheus scrapes HTTP endpoints on a fixed interval. Your Laravel app exposes a /metrics route or runs a node_exporter on the host. PHP-FPM and MySQL exporters add process-level numbers. Grafana visualises the scraped data.

# docker-compose snippet — Prometheus + Grafana on Ubuntu
services:
  prometheus:
    image: prom/prometheus:v2.55.0
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:11.3.0
    ports:
      - "3000:3000"

Define scrape targets in prometheus.yml:

scrape_configs:
  - job_name: laravel-app
    static_configs:
      - targets: ['app.internal:8080']
    metrics_path: /metrics

  - job_name: node
    static_configs:
      - targets: ['10.0.1.5:9100']

For deeper fundamentals, see the dedicated guide on Prometheus metrics monitoring. Keep label cardinality under control. Never put user IDs or order IDs into metric labels. That pattern destroys Prometheus performance within days.

Log shipping and aggregation

Laravel writes to storage/logs/laravel.log by default. Production apps should log to stdout or a structured JSON channel. A log shipper — Fluent Bit, Promtail, or Filebeat — tails files and forwards to a central store.

# config/logging.php — daily JSON channel for Laravel 12
'production_json' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel-json.log'),
    'level' => env('LOG_LEVEL', 'info'),
    'days' => 14,
    'formatter' => Monolog\Formatter\JsonFormatter::class,
],

Structured fields make correlation possible. Always include trace_id, request_id, and user_id when available. The Loki and Grafana log aggregation setup pairs well with Prometheus on small teams. For heavier volumes, Graylog centralized log management handles parsing pipelines and alerting rules.

Disk space kills more small servers than missing dashboards. Rotate logs aggressively. The guide on log rotation and disk space management covers logrotate and journalctl limits that I apply on every Deployer-managed host.

Trace instrumentation with OpenTelemetry

OpenTelemetry is the vendor-neutral standard for traces, metrics, and logs. Install the PHP SDK via Composer 2.10:

composer require open-telemetry/opentelemetry open-telemetry/exporter-otlp

Configure an OTLP exporter pointing at a collector sidecar or central instance:

# .env additions
OTEL_SERVICE_NAME=booking-api
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

Ten-percent head sampling is a sane default for most Laravel apps. Full tracing on every request doubles overhead and storage cost fast. Always sample errors at 100% by configuring tail-based sampling in the collector when budget allows.

Collection PipelineLaravel 12 AppPHP 8.3+ FPMOTel SDKOTel CollectorBatch + sampleEnrich spansPrometheusMetrics storeLokiLog storeTempoTrace storeGrafanaDashboardsAlerts
Production metrics, logs, and traces collection pipeline — OpenTelemetry collector fans out to specialised backends unified in Grafana.

Multi-service setups add complexity. Read the multi-cloud observability guide if your API, queue workers, and Redis 8.10 cache run on separate hosts or regions.

Which signal should you check first during a production outage?

Follow a consistent triage order. Random jumping between tools wastes minutes you do not have during an incident. I use this sequence on every production Laravel application I maintain.

  1. Metrics first. Open the service dashboard. Check error rate, latency percentiles, and saturation (CPU, memory, queue lag). A spike tells you scope and timing within seconds.
  2. Logs second. Filter by the incident window and severity ERROR or CRITICAL. Search for the exception class, gateway timeout, or authentication failure. Pull the stack trace and request payload.
  3. Traces third. Pick a failing trace ID from a log line. Walk the span waterfall. Identify the slow or failing downstream call — database, Redis, payment API, or external webhook.
  4. Correlate and fix. Cross-link trace_id across all three backends. Deploy the fix. Watch metrics return to baseline before you close the incident.

This order mirrors the Google SRE monitoring hierarchy: symptoms before causes, aggregates before individual events. Logs without metric context force you to grep blindly across gigabytes. Traces without logs lack error messages and business identifiers.

Incident Triage FlowAlert FiresCheck MetricsScope confirmed?Error rate, latencySearch LogsException, contextOpen TraceFind slow spanRoot Cause Found
Metrics, logs, and traces compared for incident response — check metrics for scope, logs for context, traces for latency bottlenecks.

On a legal-tech portal like Mijar Law Associates, a metric alert on 5xx rate might fire first. Logs reveal a document upload validation error. A trace shows the Spatie Media Library span timing out on a large PDF. Each signal narrows the search.

For Laravel-specific debugging, debugging Laravel in production with logs and Telescope covers safe local tooling. Never enable Telescope on a public production route without access controls. Use it in staging or as a sampled export.

How much does observability with metrics, logs, and traces cost?

Cost splits into infrastructure, storage, and engineering time. Small teams in Nepal often run everything on one or two EC2 instances. Enterprise setups push terabytes to SaaS vendors monthly.

Self-hosted stack on a single server

A t3.medium-equivalent VPS (~Rs 3,500/month, ~USD 26) can run Prometheus, Loki, Tempo, Grafana, and an OpenTelemetry collector for a moderate-traffic Laravel app. Disk is the constraint. Budget 100–200 GB SSD for 30-day retention across all three signals with sampling.

SaaS pricing reality

Commercial vendors charge primarily on log ingest volume and indexed fields. Traces bill on span count. Metrics bill on active series cardinality. A common mistake is shipping debug-level logs from every PHP-FPM worker to Datadog or New Relic. That alone can exceed Rs 50,000/month (~USD 375) on a busy eCommerce site.

Cost control tactics that actually work

  • Sample traces at 5–10% in steady state. Keep 100% on error paths.
  • Log at INFO in production. Use DEBUG only behind a feature flag.
  • Drop health-check and static asset requests from access logs.
  • Set aggressive retention: 7 days hot, 90 days cold archive for compliance logs only.
  • Cap metric label cardinality. Use logs for high-cardinality identifiers.

Compare this against the cost of one hour of undetected downtime on a booking or payment system. For most clients, a Rs 5,000–8,000/month (~USD 37–60) self-hosted stack is cheap insurance. Support and maintenance contracts often include dashboard review and alert tuning as part of ongoing ops work.

How do Laravel and PHP applications emit all three signals?

Laravel 12 on PHP 8.3 gives you hooks at middleware, event, queue, and database layers. Symfony 8.1 follows similar patterns with Monolog and its profiler bundle for development.

Application metrics from Laravel

Install a Prometheus exporter package or expose custom counters via a controller. Track business metrics alongside infrastructure ones:

// app/Http/Middleware/MetricsMiddleware.php
public function handle(Request $request, Closure $next)
{
    $start = microtime(true);
    $response = $next($request);
    $duration = microtime(true) - $start;

    Metrics::counter('http_requests_total')
        ->labels(['method' => $request->method(), 'status' => $response->status()])
        ->inc();

    Metrics::histogram('http_request_duration_seconds')
        ->observe($duration);

    return $response;
}

Pair HTTP metrics with queue metrics. A rising queue_jobs_failed_total counter often precedes user-visible errors by minutes. See API rate limiting for related middleware patterns that also belong in your metric dashboards.

Structured logging with context

Use Laravel's Log facade with context arrays. For audit trails on client portals, Laravel activity log with Spatie complements raw application logs with model-level change history.

Log::channel('production_json')->error('Payment callback failed', [
    'trace_id' => $request->header('traceparent'),
    'gateway' => 'khalti',
    'order_id' => $order->id,
    'response_code' => $response->status(),
    'duration_ms' => $duration,
]);

Validate JSON log output with the free JSON formatter tool during development. Malformed JSON breaks Loki label extraction and wastes hours in parsing pipeline debugging.

Automatic trace propagation

OpenTelemetry auto-instrumentation wraps Guzzle HTTP clients, PDO queries, and Redis calls. Ensure your queue jobs carry trace context. Without propagation, a web request trace ends at dispatch and the worker starts a disconnected trace.

// Propagate trace context into queued jobs
class ProcessPayment implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        public Order $order,
        public ?string $traceId = null,
    ) {
        $this->traceId = $traceId ?? OpenTelemetry\Context\Context::getCurrent()
            ->get(ContextKeys::traceId);
    }
}

After deploy, verify all three signals in Grafana's correlated view. Click a metric spike, pivot to logs filtered by trace_id, open the trace waterfall. If that workflow works, your instrumentation is production-ready.

Correlated View via trace_idMetric: http_errors spike at 14:32trace_id: 7a3f9c2b8e1d4f6aLog: SQLSTATE timeout on orders tableSame trace_id links log to metric windowTrace waterfall: 4.8s DB query spanHTTP 45msEloquent query 4800msRedis 3msRoot cause visible across all three signals
Metrics, logs, and traces compared in practice — shared trace_id correlates a metric spike, error log line, and slow database span.

For enterprise builds with strict SLAs, enterprise application development engagements typically include observability architecture in the initial sprint rather than as a post-launch patch. The same applies to API development where consumers expect documented SLOs and incident response runbooks.

Before your next blue-green or canary deployment, confirm metric baselines and log volume estimates. A deploy that doubles log output can fill disk overnight. Schedule testing and optimization reviews to include observability budgets alongside performance benchmarks.

Key Takeaways

  • Metrics answer "how much and how fast" — use them for dashboards, SLOs, and the first alert during any incident.
  • Logs answer "what happened" — structure them as JSON with trace_id, and retain only what compliance or debugging requires.
  • Traces answer "where time went" — sample aggressively in steady state, always capture errors, and propagate context into queue workers.
  • Correlate all three via shared identifiers (trace_id, request_id) in Grafana or your vendor's unified UI — isolated silos slow triage.
  • Control cost by limiting log verbosity, trace sampling, and metric label cardinality before you scale traffic.
  • Instrument Laravel at middleware, queue dispatch, and external HTTP boundaries — that covers 80% of production debugging scenarios.

People Also Ask

Can you use only metrics without logs or traces?

You can run a small site on metrics alone, and many teams do for years. You will detect outages quickly but diagnose them slowly. Every investigation becomes SSH plus grep through rotated files. Adding structured logs and sampled traces typically cuts mean time to resolution by half on multi-service apps.

What is the RED method versus the USE method?

RED (Rate, Errors, Duration) applies to request-driven services like Laravel APIs. USE (Utilization, Saturation, Errors) applies to resources like CPU, disk, and MySQL connections. Metrics implement both frameworks. Logs and traces add detail when RED or USE thresholds breach.

How does OpenTelemetry relate to Prometheus and Grafana?

OpenTelemetry is the instrumentation and export standard. Prometheus is a metrics backend that scrapes or receives remote-write data. Grafana visualises metrics from Prometheus, logs from Loki, and traces from Tempo. OTel sits upstream; the others are storage and visualisation layers.

Are traces worth it for a monolithic Laravel app?

Yes, even on a monolith. Traces expose N+1 query patterns, slow external payment calls, and queue handoff gaps that metrics summarise too coarsely and logs leave disconnected. Start with 10% sampling and auto-instrumented PDO plus Guzzle spans. The first slow-query discovery pays for the setup effort.

Build observability into your next release

Metrics, Logs, and Traces Compared is not an academic exercise. It is the operational foundation that separates a site you hope stays up from one you can prove stays up. Start with Prometheus metrics and structured JSON logs on your existing Ubuntu host. Add OpenTelemetry traces when external APIs or queue workers enter the picture. Correlate everything with trace_id before your next production incident, not after it.

If you want observability wired into a Laravel booking system, legal-tech portal, or eCommerce platform from day one, review the portfolio of shipped projects or reach out via contact us to discuss architecture, tooling, and realistic budgets for your traffic level. You can also explore custom software development if you need a full build with monitoring included from the first deploy.

Frequently Asked Questions

Metrics are aggregated numeric measurements over time — request rate, p95 latency, queue depth. Logs are timestamped event records with context — exceptions, payment callbacks, stack traces. Traces follow one logical request across services as a tree of timed spans. A metric tells you error rates jumped; a log tells you which exception fired; a trace tells you where time was spent in the call chain.

Metrics summarise system health over time. Logs record discrete events with context. Traces follow one request across services. Use all three together: metrics detect problems, logs explain them, traces locate the bottleneck.

Monitoring watches known failure modes through predefined dashboards and alert thresholds. Observability lets you ask new questions when something unexpected happens. Traces and structured logs unlock those ad-hoc investigations. Metrics alone often cannot answer questions you did not anticipate when you built the dashboard.

Check metrics first — error rate, latency percentiles, and saturation tell you scope and timing within seconds. Then logs — filter by the incident window and ERROR severity for stack traces and payloads. Then traces — pick a trace_id from a log line and walk the span waterfall to find the slow downstream call. Cross-link trace_id across all three before closing the incident.

A self-hosted stack on one VPS runs roughly Rs 3,500/month (~USD 26) for moderate Laravel traffic. Budget Rs 5,000–8,000/month (~USD 37–60) as practical insurance. Shipping debug logs to SaaS vendors can exceed Rs 50,000/month (~USD 375) on busy eCommerce sites.

Instrument at the source, ship centrally, and correlate with shared identifiers. Prometheus scrapes HTTP endpoints like /metrics on a fixed interval; Grafana visualises the data. Laravel logs go to structured JSON channels; Fluent Bit, Promtail, or Filebeat forward them to Loki or Graylog. OpenTelemetry PHP SDK exports traces via OTLP to a collector that fans out to Tempo or Jaeger, unified in Grafana.

Metrics: Prometheus and Grafana. Logs: Loki, Graylog, or ELK stack. Traces: Jaeger, Tempo, or Zipkin. OpenTelemetry is the vendor-neutral standard covering all three signal types. On small teams, Loki paired with Prometheus on Grafana works well. Heavier log volumes suit Graylog with parsing pipelines and alerting rules.

Never put user IDs or order IDs into metric labels — that pattern destroys Prometheus performance within days. Metrics bill on active series cardinality. High-cardinality identifiers belong in logs, not metric labels. Keep label sets small and stable: method, status code, and service name are safe; per-request or per-user labels are not.

Ten-percent head sampling is a sane default. Full tracing on every request doubles overhead and storage cost fast. Always sample errors at 100% by configuring tail-based sampling in the OpenTelemetry collector when budget allows. In steady state, 5–10% sampling keeps costs manageable while preserving error visibility.

Configure a production JSON channel in config/logging.php using Monolog JsonFormatter. Log at INFO in production; reserve DEBUG for feature-flagged debugging. Always include trace_id, request_id, and user_id when available. Use Log facade context arrays for payment failures and gateway callbacks. Malformed JSON breaks Loki label extraction and wastes hours in pipeline debugging.

Laravel 12 on PHP 8.3 exposes hooks at middleware, event, queue, and database layers. Install a Prometheus exporter package or expose custom counters via a controller. Middleware can increment http_requests_total counters and observe http_request_duration_seconds histograms labelled by method and status. Pair HTTP metrics with queue_jobs_failed_total — a rising failed counter often precedes user-visible errors by minutes.

Without propagation, a web request trace ends at job dispatch and the worker starts a disconnected trace. Pass trace_id into queued job constructors from OpenTelemetry Context::getCurrent(). Auto-instrumentation wraps Guzzle, PDO, and Redis calls, but only if context crosses the queue boundary. Otherwise you lose visibility into payment processing and other async work triggered by the original request.

Share trace_id and request_id across all three backends. In Grafana, click a metric spike, pivot to logs filtered by trace_id, then open the trace waterfall. A log line from a failed Khalti callback carries the trace_id that links to the slow payment verification span. Isolated silos force blind grepping; shared identifiers cut incident triage from minutes to seconds.

Metrics: 30–90 days. Logs: 7–30 days hot, with longer archive for compliance-only records. Traces: 7–14 days. Budget 100–200 GB SSD for 30-day retention across all three signals with sampling on a single-server stack. Aggressive rotation matters — disk space kills more small servers than missing dashboards.

Sample traces at 5–10% in steady state and keep 100% on error paths. Log at INFO, not DEBUG, in production. Drop health-check and static asset requests from access logs. Cap metric label cardinality. Set 7 days hot retention and 90 days cold archive for compliance logs only. Compare that Rs 5,000–8,000/month self-hosted cost against one hour of undetected downtime on a booking or payment system.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: