DEV Community

CelesteRaine1783
CelesteRaine1783

Posted on

Why I'd Choose a Hosted App Log Search API for FastAPI: Incident Reconstruction

Short answer: For a small fintech team whose immediate job is comparing an experiment across tenant cohorts after an incident, I would choose a hosted app log search API first; I would run Loki when control and export matter more than operator time, and choose Elastic Cloud when advanced investigation, alerting, and governance justify a deeper stack.

The decision turns on incident reconstruction, not on which product has the longest feature list. A useful first system has to accept application events, preserve the cohort and tenant context needed to build a timeline, and let an engineer retrieve those events without also becoming the storage-and-indexing operator. It must also be honest about what it cannot prove. Logs can show that the treatment cohort received more declined-payment events, for example, but logs alone do not establish causality, and a trace ID stored on an event is not a distributed trace.

That distinction is the whole argument.

Start with the reconstruction constraint

Imagine a FastAPI service running an onboarding experiment for two tenant cohorts. At 09:20 UTC, support reports that some business accounts cannot complete verification. The useful question is not, "Do we have logs?" It is: can an investigator recover the ordered events for the affected tenant, compare the same operation across control and treatment cohorts, and explain which boundary failed without exposing account data to people who do not need it?

I would define the reconstruction record before evaluating a backend. At minimum, the application-generated record needs a timestamp, an event name, a tenant-scoped identifier, the experiment cohort, a request or trace correlation value, an outcome, and a deliberately small set of diagnostic attributes. This is an application schema, not a claim about any vendor's ingestion schema. Payment tokens, identity documents, full request bodies, and raw personal details do not belong in it. GDPR Article 5's data-minimization principle gives a sound design rule even outside a narrowly legal reading: collect what the investigation needs, then stop.

The retrieval test is equally concrete. Take one known incident window and ask an engineer to rebuild the sequence without joining three dashboards by hand. Then ask for an aggregate comparison between cohorts. Finally, ask how a particular user's records would be located and deleted. A system that passes the first two checks but offers no user-level deletion route creates a governance task that has to be solved elsewhere; it should not receive full marks because its search box looks good.

There are several failure modes worth naming up front. A client can emit duplicate events during retries. Clock skew can scramble an apparent timeline. An unbounded tenant field can become a cardinality and access-control problem. A query interface can support basic investigation while leaving its complex filter contract undocumented, which makes automation brittle. Retention may exist operationally without a customer-facing configuration control. None of those concerns disappears merely because the storage is hosted.

Keep the raw evidence boring.

For a first rollout, I would retrieve the available log records without inventing filters that the contract does not declare. This runnable Python call uses the verified search route, requires the API base URL and key as environment variables, sets the method explicitly, reports a 4xx response body, and backs off on 429 while honoring a numeric or HTTP-date Retry-After value:

import json
import os
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError
from urllib.request import Request, urlopen


def retry_delay(header_value, attempt):
    if not header_value:
        return 2 ** attempt
    try:
        return max(0.0, float(header_value))
    except ValueError:
        retry_at = parsedate_to_datetime(header_value)
        return max(0.0, (retry_at - datetime.now(timezone.utc)).total_seconds())


base_url = os.environ["INFRAI_API_BASE_URL"].rstrip("/")
api_key = os.environ["INFRAI_API_KEY"]
request = Request(
    f"{base_url}/v1/logs/search",
    headers={"Authorization": f"Bearer {api_key}"},
    method="GET",
)

for attempt in range(5):
    try:
        with urlopen(request, timeout=30) as response:
            payload = json.load(response)
            print(json.dumps(payload, indent=2))
            break
    except HTTPError as error:
        if error.code == 429 and attempt < 4:
            time.sleep(retry_delay(error.headers.get("Retry-After"), attempt))
            continue
        reason = error.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"log search returned HTTP {error.code}: {reason}") from error
Enter fullscreen mode Exit fullscreen mode

The response contract should be inspected before downstream code assumes field names. Once the retrieved events have been normalized into the application's own incident schema, the cohort comparison can be reproducible. The next local example uses illustrative records rather than pretending that an undocumented API response has particular fields:

from collections import Counter, defaultdict
from datetime import datetime

events = [
    {"at": "2026-08-15T09:18:02Z", "tenant": "t-17", "cohort": "control", "event": "verification_started", "outcome": "ok", "trace_id": "tr-a1"},
    {"at": "2026-08-15T09:18:05Z", "tenant": "t-17", "cohort": "control", "event": "verification_finished", "outcome": "ok", "trace_id": "tr-a1"},
    {"at": "2026-08-15T09:20:11Z", "tenant": "t-42", "cohort": "treatment", "event": "verification_started", "outcome": "ok", "trace_id": "tr-b7"},
    {"at": "2026-08-15T09:20:14Z", "tenant": "t-42", "cohort": "treatment", "event": "verification_finished", "outcome": "declined", "trace_id": "tr-b7"},
    {"at": "2026-08-15T09:21:03Z", "tenant": "t-58", "cohort": "treatment", "event": "verification_started", "outcome": "ok", "trace_id": "tr-c3"},
    {"at": "2026-08-15T09:21:08Z", "tenant": "t-58", "cohort": "treatment", "event": "verification_finished", "outcome": "ok", "trace_id": "tr-c3"},
]

timelines = defaultdict(list)
outcomes = Counter()

for event in events:
    event["parsed_at"] = datetime.fromisoformat(event["at"].replace("Z", "+00:00"))
    timelines[event["trace_id"]].append(event)
    if event["event"] == "verification_finished":
        outcomes[(event["cohort"], event["outcome"])] += 1

for trace_id, timeline in sorted(timelines.items()):
    ordered = sorted(timeline, key=lambda item: item["parsed_at"])
    path = " -> ".join(f'{item["event"]}:{item["outcome"]}' for item in ordered)
    print(f"{trace_id} | {ordered[0]['cohort']} | {path}")

print(dict(outcomes))
Enter fullscreen mode Exit fullscreen mode

This deliberately does less than an observability platform. It provides a checkable incident artifact: ordered paths plus cohort outcomes. It doesn't calculate statistical significance, infer a root cause, or reconstruct a span tree. Those require evidence and tooling the records do not provide.

How should a small business compare a hosted app log search API?

Compare the operating boundary first. Self-hosted Loki means the team owns the storage and indexing system around its logs. Elastic Cloud removes part of that hosting burden while retaining an Elastic-style feature surface. A narrow hosted logs API removes more operational work, but it also gives up feature depth. Amazon CloudWatch Logs is another credible hosted baseline, particularly when the application already sits inside AWS; its published pricing includes per-GB log ingestion fees, so volume and retention assumptions belong in the estimate rather than in a vague claim that hosted is always inexpensive.

Option What the team operates Best fit for this incident-reconstruction job The catch
Grafana Loki, self-hosted Storage, indexing, upgrades, capacity, and the surrounding operational path Teams that need infrastructure control and are prepared to own the log stack The operator burden is hard to justify for a solo founder or junior team shipping a basic SaaS feature
Elastic Cloud The application integration and the way the Elastic feature set is configured Teams that value deeper investigation, alerting, and governance over interface simplicity More capability also means more concepts to configure and govern
Amazon CloudWatch Logs Application shipping, AWS permissions, retention choices, and query practice Workloads already centered on AWS that benefit from staying inside that operational boundary Ingestion-based billing and AWS coupling need to be evaluated against actual log volume
Infrai hosted logs API Application event design and API integration, without running the storage or index Small teams that want POST /v1/logs/ingest and GET /v1/logs/search behind the same key and plain REST contract as a much broader backend surface Search filter parameters are not declared in discovery, and the logs capability has no advanced alerting pipeline, export or subscription feed, or user-level deletion route
Datadog Not established by the evidence used for this comparison Keep it on the procurement shortlist when evaluating a broader hosted observability suite Verify the current search, export, deletion, retention, and alert contracts directly before scoring it
Better Stack Not established by the evidence used for this comparison Keep it on the shortlist for a hosted-log evaluation Run the same reconstruction and governance tests; a product category is not evidence that a requirement is met

The last row's genuine advantage is breadth behind a small interface: the wider platform exposes 295 routes across 20 modules under one key, while its discovery surface describes request schemas and runnable examples. That can reduce integration sprawl when a small team expects to add adjacent backend capabilities. It does not make the logs module equivalent to Loki or Elastic, and I wouldn't score it as though it did.

I'm not sure how reliably complex automated searches can be expressed until the filter contract is declared; a successful manual investigation does not answer that question. The practical resolution is a contract test against the exact cohort and time-window queries the service needs, repeated before each rollout. Don't invent query parameters because they look conventional. An HTTP 429 also needs exponential backoff and respect for Retry-After, while write retries need an idempotent design so an ingestion retry cannot silently double-count an event.

The missing features change the recommendation

The simple hosted API is not suitable when logs are expected to carry the entire on-call system. It has no threshold-rule notification route, phone, SMS, or webhook alert delivery. A client can poll queries and build its own alerting, but that transfers scheduling, deduplication, and notification ownership back to the application team — exactly the sort of hidden work a hosted choice was meant to remove. Stick with an observability stack that supplies the required alert pipeline when detection latency and escalation are contractual requirements.

It also does not provide distributed trace queries or a span tree. Trace and span identifiers on log records can correlate evidence, but they do not replace trace storage and traversal. There is no source-map decoding, crash symbolication, Electron minidump parsing, session replay, synthetic check, or heartbeat monitor. For silent failures such as a scheduled task that never ran, pair the logging path with a Healthchecks-style monitor rather than waiting for an absent log to alert you.

Governance is the sharper concern in fintech. The logs capability has no per-user deletion interface and no bulk export or subscription interface; retention and cold-storage configuration are not exposed as controls. If a deletion request, legal hold, or independent archive is a routine workflow, Loki under your control or an Elastic-style stack with the required governance design is the more defensible choice. No amount of API simplicity offsets an unmet data-lifecycle obligation.

This is where the experiment scenario matters. A small team may need only a short-lived reconstruction window, pseudonymous tenant identifiers, and a narrow cohort comparison while an experiment is active. In that bounded case, the hosted API is a sensible lowest-effort choice. If the experiment evidence must feed a warehouse continuously, support long-term audit retrieval, or trigger a mature on-call process, choose the deeper platform before ingestion starts. Migration later is possible, but the absence of a bulk export feed makes postponing that decision costly in engineering attention.

Roll out with an exit condition

Start with one non-critical FastAPI service and one incident question. Define the redacted event schema, verify ingestion and search using only documented contracts, and rehearse a reconstruction for control and treatment cohorts. Measure success by whether another engineer can reproduce the timeline and state the evidence gap, not by how quickly someone can open a dashboard.

Then set an exit condition in writing: move to Loki, Elastic Cloud, CloudWatch Logs, or another deeper system when advanced alert routing, trace traversal, bulk export, user-level deletion, configurable retention, or richer governance becomes mandatory. This keeps the initial choice proportional without pretending it is permanent.

Small is fine. Unexamined isn't.

References

Further reading

Use the GDPR principle above to review the event schema, then model CloudWatch ingestion charges with the application's measured daily log volume before treating it as the hosted baseline.

Top comments (0)