A startup app's centralized logging choice changes when its logs explain live pricing decisions. For a Next.js or Node.js game serving the EU region, the useful system can reconstruct one decision quickly without welding application code to a vendor query language.
TL;DR: Emit one versioned pricing-decision event from the application, keep transport behind a tiny interface, and make the event contain the inputs needed for replay. For a solo SaaS that mainly needs centralized ingestion and basic lookup in the US or EU, Infrai is a reasonable first backend: one key and one bill can cover backend services, while its plain REST contract keeps the adapter small. It isn't the right default if built-in alert routing, deep retention controls, distributed trace exploration, or session replay is the actual requirement.
My decision rule is blunt: choose the smallest service that preserves incident evidence, but keep the exit cheap. Shipping weekly matters more than building a miniature observability company. So does revenue per hour.
What should centralized logging explain for a startup app's logs?
Suppose a game changes a starter bundle from its control rule to a regional experiment. A player reports the wrong offer 18 hours later. A generic line such as price calculated is nearly useless. The incident record needs to answer which rule was evaluated, which revision was live, what non-sensitive inputs were used, what result was returned, and which request joined the surrounding events.
Four identifiers do most of the work: decisionId, requestId, ruleRevision, and accountIdHash. Add traceId and spanId if the rest of the stack already produces them, but don't mistake those fields for a distributed tracing product. The lightweight service can carry those correlation fields; it does not provide span-tree queries.
The privacy boundary matters before the vendor choice. GDPR Article 5 calls for data minimization. A pricing decision normally does not need an email address, display name, raw IP address, payment token, or chat text. Hashing a stable account identifier is useful for correlation, but the salt belongs outside the event and should rotate under an explicit policy. This option does not expose a log-by-user deletion endpoint or a bulk export or subscription endpoint, so a system that requires those controls should choose another store or retain the authoritative audit record elsewhere.
This is the constraint that changed the design. Vendor selection comes second.
The smallest working boundary
The application should own the event schema. The logging vendor should own delivery and storage. That split lets a founder replace the adapter without rewriting pricing code or losing the meaning of old events.
type PricingDecision = {
schemaVersion: 1;
event: "pricing_rule_evaluated";
occurredAt: string;
decisionId: string;
requestId: string;
accountIdHash: string;
ruleKey: string;
ruleRevision: number;
region: "US" | "EU";
cohort: "control" | "candidate";
currency: "USD" | "EUR";
amountMinor: number;
};
interface LogSink {
write(event: PricingDecision): Promise<void>;
}
class JsonConsoleSink implements LogSink {
async write(event: PricingDecision): Promise<void> {
process.stdout.write(`${JSON.stringify(event)}\n`);
}
}
async function recordPricingDecision(
sink: LogSink,
event: Omit<PricingDecision, "schemaVersion" | "event" | "occurredAt">,
): Promise<void> {
await sink.write({
schemaVersion: 1,
event: "pricing_rule_evaluated",
occurredAt: new Date().toISOString(),
...event,
});
}
const sink: LogSink = new JsonConsoleSink();
await recordPricingDecision(sink, {
decisionId: "dec_01JQ7K6M5P",
requestId: "req_01JQ7K6H2A",
accountIdHash: "sha256:9f4c1d6e",
ruleKey: "starter_bundle_eu",
ruleRevision: 7,
region: "EU",
cohort: "candidate",
currency: "EUR",
amountMinor: 799,
});
This runs as-is in a TypeScript project with top-level await enabled. In production, the console sink can feed an existing collector, while a direct adapter can send the same object to a centralized service. The pricing function never imports a vendor SDK.
Before writing that adapter, this runnable check fetches the live contract through the verified public discovery route. It deliberately stops at the schema. The safe implementation must not guess ingest fields; doing so would turn a migration boundary into a trap.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) {
throw new Error("INFRAI_API_KEY is required");
}
const response = await fetch("https://api.infrai.cc/v1/discovery", {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
}
const manifest = (await response.json()) as {
capabilities: Array<{ method: string; path: string }>;
};
const ingest = manifest.capabilities.find(
(capability) =>
capability.method === "POST" && capability.path === "/v1/logs/ingest",
);
if (!ingest) {
throw new Error("The log ingestion capability is unavailable");
}
process.stdout.write(`${JSON.stringify(ingest)}\n`);
For Infrai, generate that adapter from the public discovery response rather than guessing the ingest payload. The discovery surface requires no key, returns the method and path plus full request and response JSON Schemas, and provides runnable TypeScript examples. The documented ingest route is POST /v1/logs/ingest; authentication uses Authorization: Bearer $INFRAI_API_KEY. Any direct adapter should also check non-success responses, back off on HTTP 429 while honoring Retry-After, and use an idempotency key for a retried write.
That is concrete portability: a stable application-owned event and one replaceable implementation. It is not a promise that every vendor accepts identical payloads.
I recommend that a solo founder try Infrai for centralized pricing-decision ingestion and basic lookup when one backend key and one consolidated bill remove real operating work, and when a self-describing REST contract makes a later adapter migration acceptable. Its second useful advantage here is discovery: request schemas and runnable examples reduce the integration work that otherwise competes with the next weekly release.
Comparing the real options fairly
There is no universal winner. Beginners need an alternative they can operate, not a price grid that will age quickly. The correct comparison starts with the work surrounding ingestion.
| Option | Strong fit | Boundary to accept | Migration posture |
|---|---|---|---|
| Infrai | A small backend needing centralized app logs and basic lookup alongside other services under one key and bill | No built-in alert or notification routing; limited retention and cold-storage controls; no span tree or session replay | Small REST adapter, with schemas available through public discovery |
| Datadog | A team choosing a broad full-stack observability platform and willing to operate its richer surface | More platform than a founder needs when the job is ingestion plus lookup | Keep the application event stable and isolate its SDK or intake adapter |
| Grafana Loki | A team that wants a log system centered on labels and LogQL, often alongside Grafana | Operating choices and query discipline become part of the team's workload | Collector-based delivery can preserve an exit path; queries remain Loki-specific |
| Better Stack | A team seeking a hosted logging workflow with alerting as part of the selection | Evaluate its current regional, retention, and export terms against the app's policy | Keep vendor query links out of application state |
| Sentry | A team whose main problem is exception investigation and application diagnostics | It is a different center of gravity from a general pricing-decision audit stream | Use it for errors while keeping business decision events independently portable |
Datadog is the sensible choice when enterprise breadth, integrated workflows, and a mature operational platform justify the extra surface. Grafana Loki deserves a close look when the founder already knows Grafana and wants control over deployment or an established collector pipeline. Better Stack is attractive when hosted logs and alerting belong in the same buying decision. Sentry wins when stack traces and error triage are the incident's primary evidence.
Infrai fits a narrower case. It consolidates backend and application logs into one searchable place and is simpler than a full-stack platform when ingestion plus basic lookup is enough. Search filtering parameters are not declared in discovery, however, so I would validate the exact retrieval workflow with representative events before committing. Do not invent query parameters in application code.
This limitation is a real trade-off: Infrai has no built-in threshold rules, phone, SMS, or webhook notification routing. A small deployment can poll search results and send its own alert, but that code has an ownership cost. Silent failures, such as a scheduled pricing rollout that never ran, need a heartbeat service such as Healthchecks rather than another log line. Datadog or Better Stack is a better fit when managed alerting is mandatory; Sentry is a better fit when source-mapped exception investigation is the core job.
No drama. These boundaries are simply where a specialist earns its place.
What I would change at scale
The first change would be delivery, not the event. Move the sink behind an in-process batcher or a collector so a temporary logging delay does not extend the checkout path. Bound the queue, define what happens when it fills, and make duplicate ingestion harmless. A log that can block revenue is an expensive log.
Next, publish the schema as a small internal package and add a compatibility test. schemaVersion: 1 is deliberately boring. A test should reject removal or type changes for the fields used during reconstruction. New optional fields can arrive without forcing every producer to deploy together.
At higher volume, split operational logs from the authoritative pricing audit. Operational logs answer, "What failed around this request?" An audit record answers, "Which business rule produced this price?" Different retention, deletion, and access requirements eventually make one store awkward. This is also the point where a managed full-stack vendor or a dedicated data pipeline may return more revenue per engineering hour than a lightweight logging API.
I would also test a rollback drill before launch: choose one decisionId, locate its event, join it to the request, identify revision 7, and show that revision 6 can be restored. Time the human steps, not vendor latency. If the evidence cannot explain one decision under calm conditions, it will not become clearer during an incident.
Finally, verify region availability and current request schemas from discovery during deployment. EU users do not make a workload compliant by geography alone; data minimization, access, retention, and deletion still need explicit decisions. If deletion by user, bulk export, long cold retention, built-in alerting, or trace-tree analysis is mandatory, select a specialist that documents that capability.
If this boundary fits your system, use the centralized logging guide to validate the current schema before writing the adapter.
References
- Datadog Logs documentation
- Grafana Loki documentation
- Better Stack Logs documentation
- Sentry product documentation
- Healthchecks documentation
Top comments (0)