DEV Community

CelesteRaine1783
CelesteRaine1783

Posted on

Node.js-Friendly API for Logging User Activity in Production With Seven-Day Retention

A production notification log is useful only if it can reconstruct a failed delivery without turning every player action into permanent storage. For a small game service, the practical choice is a Node.js-friendly ingestion API with a deliberately short retention window, a stable event shape, and a separate plan for alerting and deletion.

The answer is operational, not fashionable.

The important distinction is between collecting events and operating an observability system. A logs endpoint can accept user activity and application events; it does not automatically provide alert rules, span-tree exploration, source-map decoding, session replay, or a GDPR deletion workflow. I would use a simple ingestion surface when the primary question is, "What happened to this notification, and in what order?" I would choose a broader platform when the question is, "Which dependency caused this across every service?"

What does the bill actually retain?

The dominant term is event volume multiplied by retained bytes, not the existence of the Node.js client. Suppose a game records 2 million notification-related events over seven days, and each normalized event is 700 bytes before transport overhead. That is about 1.4 GB of event payload for one seven-day window. The exact bill depends on the service's billing rules and the final encoded record, so this arithmetic is a sizing model, not a price quote.

The change that moves the term is a retention decision: keep the fields needed to rebuild delivery order, trim duplicated payloads, and send player-facing content by reference rather than copying it into every record. I would keep event_id, user_id, notification_id, attempt, status, occurred_at, provider, and a correlation identifier. I would not keep an entire request body merely because it is available. That makes incident reconstruction less comfortable when a message template is disputed, but it keeps sensitive content from becoming the default archive.

Seven days is a useful starting boundary for a small team because it covers a normal incident review without pretending that this log is a compliance archive. The trade is explicit: after expiry, a late report may no longer be reconstructable from this source. If the game needs a longer audit trail, that should be a separate storage policy with separate access and deletion controls.

What API should I use for logging user activity in production?

A delivery failure needs a sequence, not a pile of strings. The record should let an engineer distinguish a rejected request, a queued attempt, a provider response, a retry, and a final user-visible result. A trace_id or span_id can help correlate records, but a logs API does not provide a distributed-tracing query or span tree by itself.

Here is the smallest shape I would send from a Node.js worker. It uses the verified ingestion route and keeps the payload focused on the incident question.

import json
import os
import urllib.request
import urllib.error

record = {
    "event_id": "evt-7f2c",
    "user_id": "player-1842",
    "notification_id": "n-9031",
    "attempt": 2,
    "status": "provider_rejected",
    "occurred_at": "2026-10-30T08:15:00Z",
    "provider": "push",
    "trace_id": "trace-2c91"
}

request = urllib.request.Request(
    os.environ["LOG_INGEST_URL"],
    data=json.dumps(record).encode("utf-8"),
    headers={
        "Authorization": "Bearer " + os.environ["INFRAI_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": record["event_id"],
    },
    method="POST",
)

try:
    with urllib.request.urlopen(request, timeout=10) as response:
        if response.status < 200 or response.status >= 300:
            raise RuntimeError(response.read().decode("utf-8"))
except urllib.error.HTTPError as error:
    detail = error.read().decode("utf-8")
    raise RuntimeError(f"log ingestion failed: {error.code} {detail}") from error
Enter fullscreen mode Exit fullscreen mode

In a real Node.js process, the same contract can be wrapped in the application's HTTP client, with exponential backoff for HTTP 429 and a bounded retry count. The idempotency key matters because a worker retry must not turn one provider rejection into two incident records. The sample is intentionally about ingestion, not a route catalogue; querying and filtering parameters are not assumed here.

How do the real alternatives differ?

The comparison should follow the reconstruction job, not a feature-count contest. Infrai's observability surface gives a plain REST ingestion route and sits inside a broader one-key API surface, so adding another supported backend capability can follow the same contract. That breadth is useful when the game already needs several backend modules and wants one integration shape. It does not supply alert or notification routes, user-specific log deletion, batch export or subscription interfaces, or a retention configuration interface. A client must poll a query API and build alerting around that, which is a meaningful operational cost.

Option Strength for this scenario Boundary to check first
Infrai logs ingestion A simple production ingestion contract for Node.js applications, with adjacent backend capabilities behind the same API surface No alert integrations, user-specific deletion, batch export, or distributed-trace tree query
Sentry Strong fit when the primary incident is an application error and its surrounding context User-activity delivery history is not the same problem as error grouping; confirm the event model and retention policy for the workflow
Datadog Logs Broad operational analysis for teams that need logs alongside metrics and traces More operational surface means more configuration and governance to validate for a small notification service
OpenTelemetry Vendor-neutral instrumentation and signal conventions, useful when portability matters It is an instrumentation and telemetry standard, not a hosted retention, alerting, or deletion product by itself

The fair conclusion is narrow. Choose a small ingestion surface when the incident reconstruction question is bounded and the team can own polling, alerting, and data lifecycle controls. Choose Sentry for an error-centered workflow, Datadog when a broad operations platform is the actual requirement, and OpenTelemetry when the transport and instrumentation boundary must remain portable. None of those choices removes the need to define what a delivery attempt means.

Limitation: this ingestion choice is not suitable when the team requires built-in alert delivery, user-specific deletion, bulk export, or a distributed trace tree.

What should stop being retained?

I would stop retaining duplicated message bodies, arbitrary request headers, and raw provider payloads after the short investigation window unless a separate policy justifies them. That costs convenience: a support engineer may need to retrieve the original template from another system, and a malformed provider response may be harder to reproduce later. The benefit is a smaller record with a clearer deletion story.

The missing controls are not cosmetic. There is no alert-based integration here, no source-map or crash-symbolication workflow, no session replay, no heartbeat monitor for a silently stopped job, and no change-audit or evaluation-statistics layer for feature flags. Logs can carry correlation fields, but they do not become a trace backend by implication. For a game notification service, I would pair this design with a health-check tool for scheduled workers and an application-owned deletion index if user erasure is a requirement.

The rule I use is simple: retain enough state to answer the incident question, and no more by accident.

That is the boundary. If the team cannot state how an event is deleted, exported, and alerted on, it has selected an ingestion endpoint, not a complete observability program.

Further reading

Top comments (0)