Skip to content

Log-Based Metrics vs Native Metrics

What was run

The real, same question — "how many checkouts have been declined?" — answered two different ways against the exact same real traffic from orders_app.py: once by parsing the real structured log file from Lesson 8 line by line, and once by reading the native Prometheus counter that was incremented directly in the application code, in real time, as each request happened.

The real consistency check

# log-derived: parse every real log line, count matching events
count = 0
for line in open("orders_structured.log"):
    rec = json.loads(line)
    if rec.get("event") == "checkout_declined":
        count += 1
# log-based count: 4 declined events
curl -s ".../api/v1/query" --data-urlencode 'query=orders_requests_total{endpoint="/checkout", status!="200"}'
# native metric count: 4.0 declined events

Both answers agree exactly: 4. This is real, useful proof that the native counter and the structured logs are genuinely tracking the same underlying reality — the kind of cross-check this course has used before (the real charged/declined-rate consistency check in Level 1, Lesson 10) to build confidence that instrumentation is correct, not just independently present.

How It Actually Works

A native metric is incremented directly in application code, in memory, at the exact moment the real event happens (REQUEST_COUNT.labels(...).inc(), from Level 1, Lesson 3) — the cost of updating it is a single, constant-time in-memory operation, regardless of how much total traffic the service has ever handled. A log-based metric is derived after the fact by parsing log lines a service wrote for an entirely different reason (human-readable or structured record-keeping, debugging, audit trail) — the cost of deriving it scales with how much log volume has to be scanned, exactly the real, measured cost Lesson 7 demonstrated growing from milliseconds to hundreds of milliseconds as log volume grew to 100,000 real lines, and continuing to grow linearly from there.

This gives each approach a real, distinct role rather than making one strictly better:

  • Native metrics are the right choice for anything you know in advance you'll want to track continuously — request counts, latencies, error rates — because the instrumentation cost is paid once, at write time, and stays cheap forever regardless of query frequency or volume (exactly the recording-rule-style amortization argument from Level 1, Lesson 8, but even more direct, since there's no query to run at all — the number is already sitting there, pre-maintained).
  • Log-based metrics are the right choice for a question you didn't anticipate needing a dedicated counter for — deriving a brand-new metric from historical log data doesn't require redeploying the service with new instrumentation; it only requires the right fields already being present in already-collected structured logs, which is exactly why Lesson 8's structured (not free-text) logging discipline matters even when you're not deriving a metric from it today.

The real, honest tradeoff: a log-based metric is also only as fresh as the last time it was recomputed — unlike a native counter, which is correct at the instant you query it, a log-derived number reflects whatever log volume had been ingested and parsed as of whenever that derivation job last ran, introducing a real staleness window a native metric doesn't have.

Common mistakes

  • Deriving a metric from logs for a question you could have anticipated and instrumented natively from the start — paying the real, repeated, volume-scaling cost of log parsing for something a single Counter.inc() call would have tracked essentially for free.
  • Never deriving log-based metrics at all, even for genuinely unanticipated questions — insisting that every metric must be natively instrumented means redeploying code for every new question, when the data to answer many of them may already be sitting in structured logs.
  • Treating a log-derived number as equally fresh as a native one without checking when it was last recomputed — the real consistency check in this lesson worked because both were computed from the same, complete, finished dataset; a log-based metric queried mid-ingestion could legitimately disagree with a native one for reasons that are about timing, not correctness.

Exercise

Pick one real question this course's orders_app.py logs could answer that there is currently no native metric for — for example, "what's the average declined amount, in cents?" — and write a real script that derives it from the structured log file. Then add a native Prometheus Histogram or Summary tracking the same thing directly in the application code, generate a few more real declined checkouts, and confirm both approaches once again agree on the real answer.