Short answer: a hosted logging API has the smaller setup and maintenance surface for a junior developer running a small-business app; self-hosted Loki fits when control over ingestion, retention, and tenant-level cost attribution justifies a named operator.
For a logistics experiment split across tenant cohorts, start hosted, emit structured cost dimensions from day one, and set a written trigger for reconsidering that choice. The deciding metric is operator time per useful cohort comparison, not the log-storage bill by itself.
| Choice | Initial setup | Ongoing ownership | Cost attribution | Best fit |
|---|---|---|---|---|
| Hosted logging API | Send structured events, verify delivery, configure access and retention | Watch ingestion failures, quotas, access, and spend | Fast if tenant and experiment fields survive ingestion | A junior owner who needs searchable logs this week |
| Self-hosted Loki | Deploy, secure, connect storage, configure collection, then test recovery | Own upgrades, capacity, storage, access, backup, and incident response | Flexible, but the team must build and maintain the allocation method | A team with operational capacity and a clear control requirement |
That recommendation is intentionally reversible. Ship weekly. Preserve the event contract, keep the transport behind a small interface, and move only when measured operational constraints justify it. A one-person SaaS cannot afford an observability project that steals feature weeks without improving a decision.
Should a junior developer use self-hosted or hosted logging?
The job is not "collect every log." The job is to compare an experiment across logistics tenants without confusing customer mix with product effect. Imagine testing a new dispatch-planning path for three cohorts: regional couriers, cold-chain fleets, and bulky-goods carriers. Their request volume, route duration, and failure modes differ. A raw error count would make the busiest cohort look worst even if its rate improved.
Start with a narrow decision record. Each relevant event needs an experiment identifier, variant, tenant cohort, operation, outcome, duration bucket, and a stable correlation identifier. Do not put secrets, addresses, driver names, or arbitrary payloads into those fields. The event schema should answer one question: did the candidate path change the service signals for one cohort, and what did observing that cohort consume?
The four golden signals described in the Google SRE book give the comparison a useful frame: latency, traffic, errors, and saturation. Logs can support that investigation, but they do not magically turn an experiment into a valid comparison. Normalize by traffic. Separate expected business rejection from system errors. Check saturation at the same time as latency, because a faster median can hide a strained tail.
One trap is easy to miss: cost attribution can become circular. If the high-volume cohort emits more debug events, it incurs more logging cost and also looks more expensive to serve. That may be true, or it may be an instrumentation choice. Record both service activity and observed log bytes or event counts, then report them separately. Keep it boring.
The trade-off is explicit.
This comparison uses 3 cohorts and a 4-week accounting window because a single blended total hides both tenant mix and operator labor. Four weeks is a review cadence, not a universal benchmark: a lower-volume app may need a longer observation window before drawing any conclusion, while a hard compliance requirement can settle the deployment choice before volume data matters. The point is to write down the window before looking at the result.
The first criterion: whose time pays for operations
Hosted ingestion moves part of the undifferentiated work outside the app team. It does not remove ownership. Someone still has to define retention, access, redaction, alerts, and what happens when delivery fails. The advantage is narrower responsibility during setup: the developer integrates and validates an endpoint instead of also operating the storage and query path.
Self-hosting changes the failure boundary. The team owns the deployed service, its dependencies, capacity planning, upgrades, authentication, recovery, and the telemetry for the telemetry system. That control can be valuable. It also creates recurring work that competes directly with product work.
For a solo SaaS, I use a revenue-per-hour lens: what customer or operational decision becomes possible for each hour spent? A hosted API that takes less operator attention can win even when its invoice line is larger. A self-hosted system can win when data location, retention behavior, predictable control, or an existing operations practice creates enough value to cover the maintenance. No universal monthly number settles this.
Track labor explicitly for four weeks. Include integration, access reviews, failed delivery investigation, schema changes, dashboard or query maintenance, upgrades, and recovery drills. Do not call those hours "free" because the founder performed them after dinner. Money is only one ledger.
The second criterion: can you explain each tenant's share?
Cost attribution starts before ingestion. A backend cannot reliably recover a missing tenant cohort or experiment variant from an unstructured message later. The application must attach bounded, validated dimensions at the point where it knows their meaning. Keep high-cardinality identifiers available for targeted investigation, but do not make every identifier a default grouping dimension.
Use two views. The experiment view compares outcomes and golden signals by cohort and variant. The allocation view compares event count or encoded bytes by cohort, event type, and retention class. Those views answer different questions. Combining them into one score encourages bad shortcuts, such as reducing error visibility merely to make a cohort look cheaper.
The allocation rule also needs an explicit treatment for shared events. A scheduler health event may serve every tenant. Assign it to a shared platform bucket unless there is a defensible causal rule. False precision is worse than a visible shared cost.
Require the same dimensions and validation on both backends. That makes the initial choice reversible and prevents a migration from changing the experiment itself.
A small TypeScript boundary that preserves the choice
The app should depend on an event sink, not on a query product. This example validates the bounded dimensions, emits newline-delimited JSON, and makes delivery failure visible to the caller. The two sink implementations can target a hosted HTTPS collector or a self-operated collector without changing experiment code.
type Cohort = "regional" | "cold_chain" | "bulky_goods";
type Variant = "control" | "candidate";
type Outcome = "ok" | "business_reject" | "system_error";
interface ExperimentEvent {
occurredAt: string;
experiment: string;
tenantId: string;
cohort: Cohort;
variant: Variant;
operation: "plan_route" | "confirm_dispatch";
outcome: Outcome;
durationMs: number;
correlationId: string;
}
interface LogSink {
write(event: ExperimentEvent): Promise<void>;
}
class JsonHttpSink implements LogSink {
constructor(
private readonly endpoint: URL,
private readonly token: string
) {}
async write(event: ExperimentEvent): Promise<void> {
const response = await fetch(this.endpoint, {
method: "POST",
headers: {
authorization: `Bearer ${this.token}`,
"content-type": "application/x-ndjson"
},
body: `${JSON.stringify(event)}\n`
});
if (!response.ok) {
throw new Error(`Log delivery rejected with status ${response.status}`);
}
}
}
Production delivery needs a bounded buffer, timeout, retry policy with jitter, and a dead-letter or local fallback appropriate to the app's risk. For a 429 response, honor the server's retry guidance and back off rather than retrying in a tight loop. Attach an idempotency key when the receiving contract supports one, because retrying a write can otherwise double-count the cohort's volume. Decide whether log delivery may block a customer request. Usually it should not, but silently dropping every failed delivery makes the experiment evidence untrustworthy. Expose dropped-event counts and buffer saturation as operational signals.
Test the boundary with representative events. Verify redaction before transport, retry behavior after transient rejection, bounded memory under an unavailable endpoint, and preservation of cohort fields through ingestion and query. Then run a recovery exercise. A search screen working on launch day is not proof that retained evidence will be available during an incident.
The same boundary works with application ecosystems that use appenders. The Logback manual documents appenders as the component responsible for delivering logging events and also describes custom appenders. The architectural lesson transfers: keep event creation separate from delivery, then test the delivery contract.
When is self-hosting the better runner-up?
Self-hosted Loki is a candidate when the requirement is control and the business can name who operates it. Its limitation is the operational surface: it does not fit a team that cannot own upgrades, capacity, access, recovery, and the collector path. Good triggers for evaluating it include a firm data-location constraint, retention behavior that a hosted contract cannot satisfy, an existing team already operating the required infrastructure, or attribution needs that demand control of the storage and query path. These are engineering requirements, not aesthetic preferences.
A migration trigger should be measurable. Examples are repeated operator hours spent working around an external boundary, inability to enforce a required retention class, or sustained difficulty reconciling tenant usage with ingestion records. Revisit the choice during a scheduled review, not during a stressful incident.
A hosted API has the opposite limitation: an external ingestion contract can constrain retention, access, throughput, or attribution. It does not fit when those boundaries conflict with a documented business requirement. It remains the smaller operational surface when one junior developer owns both product delivery and operations, the contract meets access and retention requirements, and the cohort report can be produced from the structured dimensions.
Outsource the undifferentiated.
Do not migrate because one bill looks annoying. First determine whether excess volume comes from useful evidence, accidental debug noise, duplicated delivery, or unbounded fields. A backend swap does not fix a careless event contract.
Decision rule
Start hosted for the small logistics app. After four weeks, compare operator hours, delivery reliability, cohort-attributable volume, access requirements, and recovery confidence. Move to self-hosting only if control has a named business value and maintenance has a named owner.
That rule keeps the architecture honest. The logs exist to support a tenant-cohort decision; the company does not exist to operate logs.
Top comments (0)