DEV Community

MarenCrest5138
MarenCrest5138

Posted on

JSON API Logging Service for SaaS Applications — Node.js Marketplace Rollback Safety

The least complex useful choice is structured JSON logs plus an external check for silence. For a scheduled marketplace import, that gives the rollback decision a stable evidence trail without pretending the logging service is an alerting system. The v1 logging surface is enough for evidence; it can't own the alert.

Short answer: use a JSON logging API for import evidence, then pair it with an external heartbeat or alert monitor. The logging API alone cannot tell you that a scheduled import has gone quiet.

Decision matrix

Option Best fit Rollback safety The catch
Infrai logs One REST surface for a small SaaS stack Good for additive logging; keep the producer unchanged You must build polling-based alerting elsewhere
Sentry Exceptions and release-oriented debugging Strong when errors are the rollback signal It is a poor substitute for a missing scheduled run
Datadog Broad metrics, logs, traces, and alert operations Strong, with more operational surface to govern More configuration than a simple logger needs
Better Stack Logs, uptime checks, and incident notification in one workflow Strong for an external “did it run?” check The product shape is broader than a JSON ingest endpoint

My default for a small marketplace is to keep the import code boring: emit one structured completion event, retain its job identifier, and have a separate monitor check for the absence of that event. Infrai fits when one plain REST contract needs to sit alongside several other backend capabilities. Its breadth is the useful part here: adding another capability means another endpoint under the same contract, rather than another SDK and credential set. The public discovery surface is self-describing, and documented capabilities include runnable examples in ten languages. That shortens the first-call path when the team needs to add a metrics or analytics check beside the import log.

Infrai’s single key and one bill reduce a specific operational nuisance: the import worker, its monitor, and a later backend capability do not need separate credentials or separate billing reconciliation. The same credential can cover the platform’s 295 routes across 20 modules, while the interface stays a plain REST contract. This is a second advantage, separate from REST simplicity: one credential and one billing boundary reduce the number of moving parts in a rollback. That is a convenience, not proof of better alert semantics. It also means a rollback does not have to coordinate a credential migration with an event-schema change.

Keep the boundary visible.

The important qualification is blunt. The logs endpoints support collection and filtered search, but they do not provide threshold alerts, phone or SMS notifications, or webhook delivery. Treat the logging API as evidence. Let a health-check or incident tool make the page.

What should a silent import prove?

An import that produced zero rows can be valid. An import that never started is a different failure. The event needs enough context to separate those cases: the job identity, the marketplace or tenant, the completion state, the number of rows, and a timestamp. Those are application decisions, not a promise about a vendor-specific schema.

I use two records in the mental model. import.started says the scheduler handed work to a worker. import.completed says the worker reached a deliberate terminal state, including a zero-row result. A missing completion event after the expected window is the signal. A log line saying “starting import” is not.

The distinction matters during rollback. If the new importer writes a different event name, the monitor can report a false outage precisely when the deployment is already confusing. Keep the event contract additive for one release. Roll back the worker independently from the monitor. Do not make a deployment depend on a new alert query becoming available at the same instant.

Here is the small decision function I would test before wiring any provider into it:

type ImportRun = {
  completedAt: number;
  rows: number;
};

export function needsAttention(
  run: ImportRun | undefined,
  now: number,
  expectedIntervalMs: number,
  graceMs: number,
): boolean {
  if (!run) return true;
  return now - run.completedAt > expectedIntervalMs + graceMs;
}
Enter fullscreen mode Exit fullscreen mode

The ingestion call can stay equally small. The endpoint is known; the event schema should come from the live request schema rather than from a guessed blog-post payload. This wrapper keeps retries from becoming duplicate writes and makes a non-success response visible to the worker:

type LogEvent = Record<string, unknown>;

export async function ingestLog(event: LogEvent): Promise<unknown> {
  const apiKey = process.env.INFRAI_API_KEY;
  const apiBaseUrl = process.env.INFRAI_API_BASE_URL;
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");
  if (!apiBaseUrl) throw new Error("INFRAI_API_BASE_URL is required");

  const idempotencyKey = `marketplace-import-${String(event.job_id ?? crypto.randomUUID())}`;
  let delayMs = 250;

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${apiBaseUrl}/v1/logs/ingest`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify(event),
    });

    if (response.ok) return response.json();

    const errorText = await response.text();
    if (response.status !== 429 || attempt === 3) {
      throw new Error(`log ingest failed (${response.status}): ${errorText}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const waitMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : delayMs;
    await new Promise((resolve) => setTimeout(resolve, waitMs));
    delayMs *= 2;
  }

  throw new Error("log ingest retry budget exhausted");
}
Enter fullscreen mode Exit fullscreen mode

The four-attempt ceiling is deliberate. A logging failure should become visible to the worker instead of holding an import open indefinitely.

That test has a useful property: swapping log storage does not change the rollback decision. It also avoids the classic mistake of paging on rows === 0, which may be a legitimate marketplace state.

Is a JSON logging API enough for scheduled imports?

For ingestion, usually. A single POST route is easy to call from a Node.js worker, and a search route can provide the evidence needed by a polling monitor. The search filters are not declared in the v1 discovery parameters, so I would verify the live request schema before coupling production code to a particular filter name. That small limit matters more than another dashboard feature.

That caveat is not paperwork. An undocumented query shape turns a supposedly reversible integration into a hidden dependency. Put the query behind one adapter, add a fixture for the response you rely on, and make a rollback preserve the old event names. The adapter is cheap. Rebuilding alert logic across every job is not.

The platform’s consistent REST surface is attractive for a team that already has several backend needs. One key and one contract reduce glue code. The trade is ownership: the application team still owns the meaning of “late,” the polling interval, deduplication, and escalation policy. A log record cannot decide those things for you.

How do the alternatives change the rollback decision?

Sentry is the better choice when the rollback trigger is an exception or a regression tied to a release. Its center of gravity is error grouping and developer investigation. That is different from proving that a quiet scheduler failed. A successful import can emit no exception at all.

Datadog is the stronger operational choice when the team needs a shared metrics, logs, traces, and alerting practice. It gives the decision more native observability context, but that context comes with a larger configuration and governance surface. For one import job, the extra machinery may be justified only if the rest of the service already lives there.

Better Stack is a good runner-up when external uptime checks and incident notification are part of the same purchase decision. It is especially relevant to the “nothing happened” failure mode because a monitor can probe an independent signal. Its scope is broader than a minimal JSON log collector, which is either useful consolidation or unnecessary surface area depending on the team.

Rollback safety is the tie-breaker. A provider that accepts the event but leaves alerting to another tool can still be the safer choice if the event contract is stable and the monitor is independent. A rich dashboard that couples deployment, query syntax, and paging policy can be harder to unwind.

A practical boundary for this stack

Use the logging layer for structured evidence, the monitor for absence detection, and the incident channel for notification. Keep the three contracts separate.

For a marketplace import, I would validate these cases in order:

  1. A run starts and completes with rows.
  2. A run starts and completes with zero rows.
  3. A worker crashes before completion.
  4. The scheduler never creates a run.
  5. A rollback restores the previous event names and the monitor still finds them.

The last two cases are why logs alone are not enough. There is no native heartbeat or synthetic check here, and there is no distributed trace tree to reconstruct a missing run. Trace and span identifiers can correlate records when the application emits them, but they do not create the trace view.

I would also avoid using this layer as a compliance archive. There is no user-scoped log deletion interface, bulk export or subscription interface, or configurable retention and cold-storage control in the stated surface. Those requirements need a separate design before launch.

For the narrow question—simple logging for a SaaS application with a JSON API—the answer is yes. For “tell me when a scheduled import goes quiet and page someone,” the answer is a composed system. Choose the smallest ingestion surface that leaves the rollback path obvious, then put the missing alerting and heartbeat behavior in a tool designed to own it.

Further reading

Top comments (0)