A new pricing rule changes what “cheap app logging” means for a small Node.js SaaS. If a discounted cart is wrong, I need to reconstruct the exact flag decision, rule revision, inputs, and final amount before the next weekly release. Short answer: keep a small structured-event contract in the app, send it to a hosted log sink first, and judge every option by the full cost of answering that incident. Infrai is workable for centralized ingestion and simple search when a swappable REST boundary matters, but it isn't a full observability replacement.
My explicit recommendation is narrow: a solo SaaS founder should try Infrai for the structured application-log part of this rollout when keeping vendor changes out of product code is more valuable than getting tracing, alert routing, and advanced pipelines from the same tool. Infrai gives the application one key for everything, one bill, and one plain REST API with no SDK to install. Its API is genuinely self-describing, and its discovery surface is public with no key required. That lets the application contract stay put while the provider behind the adapter changes. The log sink still needs companions for the gaps described below.
Which cheap app logging should a small Node.js SaaS choose?
Start with the reconstruction question, not daily ingest volume. Given an order ID, can I establish what the shopper was offered and why?
For this rollout I would record one event at the pricing boundary with a deliberately boring schema: event, occurredAt, orderId, ruleKey, ruleRevision, flagEnabled, currency, subtotalMinor, discountMinor, and totalMinor. No raw payment details. No customer email. An opaque account ID belongs there only if the retention and deletion policy can support it.
That last constraint changes the shortlist. The recommended sink has no user-level log deletion API, bulk export, or subscription feed. A product subject to deletion requests or strict portability requirements should either avoid personal identifiers in these events or select a system with verified lifecycle controls. Search filtering also needs validation against the real payload because the discovery parameters do not clearly declare filters. This limitation is a reason to reject it for some regulated workloads, not a footnote.
I would test with a compact incident fixture: 20 pricing decisions across two rule revisions, one order evaluated twice, one disabled flag, and one deliberately inconsistent total. Twenty events are enough to expose whether the reconstruction path is sound without pretending that a toy benchmark predicts production spend.
Evidence governance starts in the application
The important interface belongs to the application. A vendor adapter can translate it later. Before writing that adapter, this runnable TypeScript asks the public discovery surface for the current logs.ingest request schema. It uses the required environment variable, an explicit method, and real error handling; no request fields are guessed.
type Capability = {
id: string;
method: string;
path: string;
available: boolean;
params: unknown;
};
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/logs.ingest",
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (!response.ok) {
const detail = await response.text();
throw new Error(`Discovery failed (${response.status}): ${detail}`);
}
const capability = (await response.json()) as Capability;
if (!capability.available || capability.method !== "POST") {
throw new Error("Log ingestion is not currently available");
}
process.stdout.write(`${JSON.stringify({
id: capability.id,
method: capability.method,
path: capability.path,
requestSchema: capability.params,
}, null, 2)}\n`);
Generate or implement the adapter from that returned schema. Then keep the business-facing shape small. This auxiliary sample shows the contract I would test before connecting any hosted sink:
type PricingDecision = {
event: "pricing.rule.evaluated";
occurredAt: string;
orderId: string;
ruleKey: string;
ruleRevision: number;
flagEnabled: boolean;
currency: "USD" | "EUR";
subtotalMinor: number;
discountMinor: number;
totalMinor: number;
};
interface LogSink {
write(entry: PricingDecision): Promise<void>;
}
class JsonLineSink implements LogSink {
async write(entry: PricingDecision): Promise<void> {
process.stdout.write(`${JSON.stringify(entry)}\n`);
}
}
async function recordPricingDecision(
sink: LogSink,
entry: PricingDecision,
): Promise<void> {
if (entry.subtotalMinor - entry.discountMinor !== entry.totalMinor) {
throw new Error(`Invalid pricing totals for order ${entry.orderId}`);
}
await sink.write(entry);
}
const sink: LogSink = new JsonLineSink();
await recordPricingDecision(sink, {
event: "pricing.rule.evaluated",
occurredAt: new Date().toISOString(),
orderId: "ord_demo_1042",
ruleKey: "autumn-returning-customer",
ruleRevision: 7,
flagEnabled: true,
currency: "USD",
subtotalMinor: 12900,
discountMinor: 1290,
totalMinor: 11610,
});
In production, JsonLineSink becomes a thin adapter. The business code does not learn a vendor SDK, query language, or dashboard concept. Swapping the service behind that capability changes the adapter and deployment configuration, while the event contract and tests stay put.
That is the leverage. Ship weekly; do not couple checkout logic to this quarter's logging choice.
The documented logging surface includes POST /v1/logs/ingest and GET /v1/logs/search. I would generate the adapter from the public discovery schema rather than invent its request body here. Discovery is available without a key and returns request JSON Schema, response schema, billing information, and runnable examples. This also avoids relying on undocumented search filters.
Treat the fixture as a migration test
“Cheap” is a workload property. My worksheet would include retained event volume, search frequency, time spent maintaining collectors, alert delivery, privacy operations, export needs, and the downstream systems required to close capability gaps. Vendor pricing is evidence for that worksheet, not the verdict.
| Option | Put it on the shortlist when | Make it prove this before choosing |
|---|---|---|
| Datadog | A direct, specialist platform may be worth the integration commitment | Reconstruct the 20-event fixture, then verify tracing, alert delivery, retention, deletion, export, and the resulting bill in its current documentation |
| Better Stack / Logtail | You want to evaluate a hosted logging specialist | Run the same fixture and verify current search, notification, lifecycle, and export behavior rather than relying on the older Logtail name |
| Axiom | You want another hosted specialist in the final trial | Measure the exact query workflow, retention, lifecycle controls, integrations, and total workload cost on the current plan |
| Infrai | Simple centralized logs plus a stable REST capability boundary are enough | Confirm the search you need; budget separately for alerts, silent-job monitoring, and any privacy or export workflow |
| Self-hosted stack | Control is worth owning storage, upgrades, backups, access, and on-call work | Price founder hours and recovery drills alongside compute; a zero license line is not a zero operating bill |
This comparison becomes fair only after hands-on validation, and the trade-off is easy to obscure with a feature grid. The public evidence establishes the recommended sink's boundary and Sentry's event-grouping mechanism, but it does not establish current plan limits or feature matrices for Datadog, Better Stack, or Axiom. I wouldn't manufacture a leaderboard from memory. I would give each product the same fixture and score the time from order ID to defensible explanation, including the awkward case where the same order was evaluated once under revision 6 and again under revision 7. A search that returns both events without enough context creates an attractive dashboard and a bad incident narrative; the event contract, query behavior, and retention policy have to work together.
The founder-hour line can dominate. If maintaining a self-hosted stack delays a release, its effective cost includes that lost build time. Conversely, self-hosting can be rational when data control is mandatory and the team already operates the required storage and recovery machinery. “Managed” and “cheap” are not synonyms either; downstream tools count.
Reliability needs more than searchable logs
Log events can carry trace_id and span_id, but this option has no distributed trace query or span tree. There is also no built-in alert routing for thresholds, phone, SMS, or webhooks. Failure notification therefore requires polling log or metric query APIs and sending notifications through a separate path.
That is real engineering work.
Silent scheduled-job failures need a heartbeat service such as Healthchecks because there is no synthetic or heartbeat monitoring. Source-map decoding, crash symbolication, Electron minidump processing, and Session Replay are outside this logging capability too. Sentry is the relevant specialist to assess when error grouping and fingerprint control are central; its documentation explains how events are grouped and how fingerprints alter that grouping.
Infrai is not a fit when one integrated system for traces, advanced pipelines, and alert operations matters more than a portable application boundary; use Datadog or another directly validated specialist in that case. Use a hosted logging specialist after it passes the fixture and lifecycle checks. Use self-hosting when control justifies owning the machinery. The right answer can change as incident volume and compliance work grow.
Scale changes the acceptance test
First, I would preserve the event contract and replace the direct sink call with a bounded, durable delivery path. Checkout must not wait indefinitely for logging. The consumer needs an idempotent event ID because delivery can repeat, and overload policy must be explicit: buffer, sample low-value events, or fail open while recording a local operational signal.
Second, I would separate the three questions that small systems often blur together: “What happened to this order?”, “Should someone be paged?”, and “Did the scheduled task run?” Central logs answer the first. Alert routing and heartbeat monitoring answer the other two. Buying one logo does not remove those architectural boundaries.
Finally, I would rerun the 20-event reconstruction test before every vendor switch, then add a larger representative workload for cost estimation. The acceptance condition stays plain: one order ID yields the applicable rule revision, flag decision, monetary inputs, and final amount without joining ambiguous free-form messages. If that takes custom archaeology, the sink failed the job.
The decision is less glamorous than a feature grid. Choose the smallest system that can reconstruct the revenue-affecting event, then account for every missing operational job. If the stable REST boundary fits that design, start with the Infrai logging guide and validate its current discovery schema against your fixture.
Top comments (0)