Skip to content

fix(observability): stop local dev from polluting prod Axiom datasets#546

Merged
izadoesdev merged 1 commit into
stagingfrom
izadoesdev/gate-dev-axiom-telemetry
Jul 8, 2026
Merged

fix(observability): stop local dev from polluting prod Axiom datasets#546
izadoesdev merged 1 commit into
stagingfrom
izadoesdev/gate-dev-axiom-telemetry

Conversation

@izadoesdev

@izadoesdev izadoesdev commented Jul 8, 2026

Copy link
Copy Markdown
Member

Problem

Every service's evlog→Axiom drain ran in development too, so local dev shipped wide events to the production Axiom datasets:

  • api, basket, uptime: called batchedAxiomDrain(ctx) unconditionally.
  • insights, slack: gated only on the Axiom key being present — which a local .env usually has.

Impact, observed: the Jul 3–4 "5,856 api server errors on deploy day" was 100% environment: "development" — local Bun Cannot find module '@databuddy/services/...' failures from unbuilt workspace packages. Production errors stayed flat (785→498→200→single digits). Dev noise outweighed real prod errors >4:1 in the shared dataset, so error-rate views (and any alert built on them) misread it as a production incident.

Fix

Gate the Axiom drain on NODE_ENV !== "development" in all five services. Dev still writes to local FS logs (.evlog/logs); prod behavior is unchanged.

Verification

check-types green for all five apps; lint clean. Behavioral: in dev the drain is now a no-op/null; prod path untouched.

Follow-up (separate)

Consider centralizing this in a shared createAxiomDrain wrapper so new services can't reintroduce the leak.


Summary by cubic

Stop local development from draining telemetry to the production Axiom datasets. This removes dev noise from prod metrics and alerts; production behavior is unchanged.

  • Bug Fixes
    • Gate the Axiom drain on NODE_ENV !== "development" across api, basket, insights, slack, and uptime.
    • In development, logs write to local .evlog files only; in production, the Axiom drain runs as before.

Written for commit 7fab18d. Summary will update on new commits.

Review in cubic

…d Axiom datasets

Every service's evlog drain shipped wide events to its production Axiom dataset
even in development: api/basket/uptime called batchedAxiomDrain unconditionally,
and insights/slack gated only on the Axiom key being present (which a local .env
often has). Result: dev noise polluted prod telemetry — e.g. a Jul 3-4 'spike' of
5,856 api errors was entirely local Bun module-resolution failures, dwarfing real
prod errors >4:1 and misreading as a production incident.

Gate the Axiom drain on NODE_ENV !== 'development' in all five services. Dev still
writes to local FS logs; prod is unchanged.
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
databuddy-status Ready Ready Preview, Comment Jul 8, 2026 12:44am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
dashboard Skipped Skipped Jul 8, 2026 12:44am
documentation Skipped Skipped Jul 8, 2026 12:44am

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 83664941-2ea5-4793-87ec-c96adc9eee37

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch izadoesdev/gate-dev-axiom-telemetry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@unkey-deploy

unkey-deploy Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Unkey Deploy

Name Status Preview Inspect Updated (UTC)
api (preview) Ready Visit Preview Inspect Jul 8, 2026 12:45am

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR gates the Axiom log drain behind NODE_ENV !== "development" across all five services (api, basket, uptime, insights, slack) to stop local dev traffic from polluting shared production datasets — a fix for the observed July 3–4 incident where >4× more development events than production events landed in the api Axiom dataset.

  • insights and slack use the cleaner pattern of conditionally constructing the drain at module load time (drain is null in dev), preventing the Axiom client and pipeline timer from ever being initialized.
  • api, basket, and uptime gate at the call site with a drainToAxiom boolean, but still eagerly construct the drain pipeline (and its interval timer) in every environment.
  • All five files use !== "development" rather than === "production", meaning NODE_ENV=test or any other non-"development" value would still drain to Axiom.

Confidence Score: 3/5

The dev-drain leak is fixed for development, but the guard allows any other non-production value (e.g. test, ci) to drain to Axiom, which could reintroduce a quieter version of the same problem.

The core fix is correct and stops the exact incident from recurring in dev. However, the !== "development" predicate means that test or CI runs with any other NODE_ENV value would still push events to the production Axiom dataset. Additionally, api, basket, and uptime still eagerly initialize the Axiom client and pipeline timer in dev, unlike the cleaner null-at-construction approach used by insights and slack.

All five evlog files share the !== "development" predicate; apps/api/src/lib/evlog-api.ts, apps/basket/src/lib/evlog-basket.ts, and apps/uptime/src/lib/evlog-uptime.ts also have the eager drain construction pattern.

Important Files Changed

Filename Overview
apps/api/src/lib/evlog-api.ts Adds drainToAxiom flag to guard batchedAxiomDrain(ctx) at call site; drain pipeline is still eagerly initialized in dev and !== "development" leaves test/staging environments unblocked.
apps/basket/src/lib/evlog-basket.ts Identical pattern to api: call-site guard added, drain still eagerly constructed, same !== "development" caveats.
apps/uptime/src/lib/evlog-uptime.ts Identical pattern to api/basket: call-site guard added, drain still eagerly constructed, same !== "development" caveats.
apps/insights/src/lib/evlog-insights.ts Cleaner approach: drain is set to null at construction time when in dev, preventing pipeline and Axiom client initialization entirely; shares the !== "development" concern.
apps/slack/src/lib/evlog-slack.ts Same construction-time null approach as insights; shares the !== "development" concern.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Logger Drain Called] --> B{NODE_ENV?}

    B -->|"=== 'development'"| C[devFsDrain only]
    B -->|"!== 'development'\n(production, test, staging, ...)"| D[devFsDrain if enabled]

    D --> E[batchedAxiomDrain]
    D --> F[batchedSuperlogDrain]

    C --> G[Local .evlog/logs]
    E --> H[Axiom Dataset]
    F --> I[Superlog]

    style C fill:#90EE90
    style E fill:#FFB347
    style B fill:#87CEEB
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Logger Drain Called] --> B{NODE_ENV?}

    B -->|"=== 'development'"| C[devFsDrain only]
    B -->|"!== 'development'\n(production, test, staging, ...)"| D[devFsDrain if enabled]

    D --> E[batchedAxiomDrain]
    D --> F[batchedSuperlogDrain]

    C --> G[Local .evlog/logs]
    E --> H[Axiom Dataset]
    F --> I[Superlog]

    style C fill:#90EE90
    style E fill:#FFB347
    style B fill:#87CEEB
Loading

Comments Outside Diff (1)

  1. apps/api/src/lib/evlog-api.ts, line 15-18 (link)

    P2 Drain pipeline still eagerly initialized in dev

    batchedAxiomDrain is unconditionally constructed at module load time in api, basket, and uptime. This means createAxiomDrain() is still called and the pipeline's internal interval timer (intervalMs: 5000) is still created and running even when drainToAxiom is false. Compare with insights and slack, where the drain is set to null at construction time — cleaner and ensures no Axiom client is initialized in dev at all. This also means flushBatchedApiDrain (line 117) calls batchedAxiomDrain.flush() unconditionally in dev, even though the buffer is empty.

    The same applies to apps/basket/src/lib/evlog-basket.ts and apps/uptime/src/lib/evlog-uptime.ts.

Reviews (1): Last reviewed commit: "fix(observability): stop local dev from ..." | Re-trigger Greptile

const useLocalEvlogFiles =
process.env.NODE_ENV === "development" || readBooleanEnv("API_EVLOG_FS");

const drainToAxiom = process.env.NODE_ENV !== "development";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 NODE_ENV !== "development" permits non-production environments

The guard !== "development" allows "test", "staging", or any other unrecognized value to drain to Axiom. If CI runs are executed with NODE_ENV=test (a common Jest/Bun test default), they would write to the production Axiom dataset just as dev traffic did before this fix. A more conservative allowlist — process.env.NODE_ENV === "production" — would ensure only intentionally production-deployed processes drain. The same concern applies to the identical guard in apps/basket/src/lib/evlog-basket.ts, apps/uptime/src/lib/evlog-uptime.ts, apps/insights/src/lib/evlog-insights.ts, and apps/slack/src/lib/evlog-slack.ts.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 5 files

Confidence score: 3/5

  • In apps/uptime/src/lib/evlog-uptime.ts and apps/api/src/lib/evlog-api.ts, the current NODE_ENV !== "development" style guard can route non-production runs (unset/typo/test envs) into production Axiom, risking telemetry pollution and noisy operational signals if merged as-is — switch to a fail-closed allowlist (production-only) before merging.
  • In apps/insights/src/lib/evlog-insights.ts, env.NODE_ENV and process.env.NODE_ENV are checked inconsistently, so local dev can miss the .evlog/logs fallback and silently lose expected local logging behavior — align both checks to the same normalized env source (or pass it through explicitly) before merging.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/insights/src/lib/evlog-insights.ts">

<violation number="1" location="apps/insights/src/lib/evlog-insights.ts:28">
P2: Local insights dev runs can now lose the intended `.evlog/logs` fallback: `env.NODE_ENV` defaults to `development` and disables Axiom, but `fsDrain` still checks raw `process.env.NODE_ENV`, and the dev script does not set it. Consider aligning the FS drain check to `env.NODE_ENV` or setting `NODE_ENV=development` in the insights dev script.</violation>
</file>

<file name="apps/uptime/src/lib/evlog-uptime.ts">

<violation number="1" location="apps/uptime/src/lib/evlog-uptime.ts:33">
P2: Direct local uptime runs can still ship to Axiom when `NODE_ENV` is unset, because this guard enables Axiom for every value except the exact string `development`. Consider fail-closing to production only so a missing local env var does not pollute the production datasets again.</violation>
</file>

<file name="apps/api/src/lib/evlog-api.ts">

<violation number="1" location="apps/api/src/lib/evlog-api.ts:33">
P2: The denylist guard `!== "development"` still permits non-production environments (e.g. `NODE_ENV=test` during CI, or an unset/typo'd value) to drain to the production Axiom dataset — the same class of pollution this PR aims to prevent. An allowlist check like `process.env.NODE_ENV === "production"` would be more defensive, ensuring only intentionally deployed processes write to Axiom. The same pattern is repeated in `apps/basket`, `apps/uptime`, `apps/insights`, and `apps/slack`.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant App as Service (api/basket/uptime)
    participant Evlog as evlog Logger Drain
    participant FS as Local FS (.evlog/logs)
    participant Axiom as Axiom Dataset (prod)
    participant Superlog as Superlog Drain

    Note over App,Superlog: Logger drain flow for each service

    App->>Evlog: loggerDrain(ctx)

    alt NODE_ENV === "development"
        Evlog->>FS: devFsDrain(ctx)
        Note over Evlog: drainToAxiom = false
        Evlog->>Superlog: batchedSuperlogDrain?.(ctx)
        FS-->>App: Local log written
    else NODE_ENV !== "development" (prod/staging)
        Evlog->>FS: devFsDrain(ctx) (if configured)
        Evlog->>Axiom: batchedAxiomDrain(ctx)
        Evlog->>Superlog: batchedSuperlogDrain?.(ctx)
        Axiom-->>Evlog: Event ingested
    end

    participant Insights as insights Service
    participant Slack as slack Service

    Note over Insights,Slack: Similar flow with pipeline wrapper

    Insights->>Evlog: loggerDrain(ctx)
    alt NODE_ENV === "development" OR no axiomApiKey
        Evlog->>Superlog: batchedSuperlogDrain?.(ctx)
        Note over Evlog: batchedAxiomDrain is null
    else prod with axiomApiKey
        Evlog->>Axiom: batchedAxiomDrain(ctx)
        Evlog->>Superlog: batchedSuperlogDrain?.(ctx)
        Axiom-->>Evlog: Event ingested
    end

    Slack->>Evlog: loggerDrain(ctx)
    alt NODE_ENV === "development" OR no axiomApiKey
        Evlog->>Superlog: batchedSuperlogDrain?.(ctx)
        Note over Evlog: batchedAxiomDrain is null
    else prod with axiomApiKey
        Evlog->>Axiom: batchedAxiomDrain(ctx)
        Evlog->>Superlog: batchedSuperlogDrain?.(ctx)
        Axiom-->>Evlog: Event ingested
    end
Loading

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

)
: null;
const batchedAxiomDrain =
axiomApiKey && env.NODE_ENV !== "development"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Local insights dev runs can now lose the intended .evlog/logs fallback: env.NODE_ENV defaults to development and disables Axiom, but fsDrain still checks raw process.env.NODE_ENV, and the dev script does not set it. Consider aligning the FS drain check to env.NODE_ENV or setting NODE_ENV=development in the insights dev script.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/insights/src/lib/evlog-insights.ts, line 28:

<comment>Local insights dev runs can now lose the intended `.evlog/logs` fallback: `env.NODE_ENV` defaults to `development` and disables Axiom, but `fsDrain` still checks raw `process.env.NODE_ENV`, and the dev script does not set it. Consider aligning the FS drain check to `env.NODE_ENV` or setting `NODE_ENV=development` in the insights dev script.</comment>

<file context>
@@ -24,15 +24,16 @@ const pipeline = createDrainPipeline<DrainContext>({
-		)
-	: null;
+const batchedAxiomDrain =
+	axiomApiKey && env.NODE_ENV !== "development"
+		? pipeline(
+				createAxiomDrain({
</file context>

const useLocalEvlogFiles =
process.env.NODE_ENV === "development" || readBooleanEnv("UPTIME_EVLOG_FS");

const drainToAxiom = process.env.NODE_ENV !== "development";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Direct local uptime runs can still ship to Axiom when NODE_ENV is unset, because this guard enables Axiom for every value except the exact string development. Consider fail-closing to production only so a missing local env var does not pollute the production datasets again.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/uptime/src/lib/evlog-uptime.ts, line 33:

<comment>Direct local uptime runs can still ship to Axiom when `NODE_ENV` is unset, because this guard enables Axiom for every value except the exact string `development`. Consider fail-closing to production only so a missing local env var does not pollute the production datasets again.</comment>

<file context>
@@ -30,6 +30,8 @@ const devFsLogsDir = join(
 const useLocalEvlogFiles =
 	process.env.NODE_ENV === "development" || readBooleanEnv("UPTIME_EVLOG_FS");
 
+const drainToAxiom = process.env.NODE_ENV !== "development";
+
 const devFsDrain = useLocalEvlogFiles
</file context>
Suggested change
const drainToAxiom = process.env.NODE_ENV !== "development";
const drainToAxiom = process.env.NODE_ENV === "production";

const useLocalEvlogFiles =
process.env.NODE_ENV === "development" || readBooleanEnv("API_EVLOG_FS");

const drainToAxiom = process.env.NODE_ENV !== "development";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The denylist guard !== "development" still permits non-production environments (e.g. NODE_ENV=test during CI, or an unset/typo'd value) to drain to the production Axiom dataset — the same class of pollution this PR aims to prevent. An allowlist check like process.env.NODE_ENV === "production" would be more defensive, ensuring only intentionally deployed processes write to Axiom. The same pattern is repeated in apps/basket, apps/uptime, apps/insights, and apps/slack.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/lib/evlog-api.ts, line 33:

<comment>The denylist guard `!== "development"` still permits non-production environments (e.g. `NODE_ENV=test` during CI, or an unset/typo'd value) to drain to the production Axiom dataset — the same class of pollution this PR aims to prevent. An allowlist check like `process.env.NODE_ENV === "production"` would be more defensive, ensuring only intentionally deployed processes write to Axiom. The same pattern is repeated in `apps/basket`, `apps/uptime`, `apps/insights`, and `apps/slack`.</comment>

<file context>
@@ -30,6 +30,8 @@ const devFsLogsDir = join(
 const useLocalEvlogFiles =
 	process.env.NODE_ENV === "development" || readBooleanEnv("API_EVLOG_FS");
 
+const drainToAxiom = process.env.NODE_ENV !== "development";
+
 const devFsDrain = useLocalEvlogFiles
</file context>
Suggested change
const drainToAxiom = process.env.NODE_ENV !== "development";
const drainToAxiom = process.env.NODE_ENV === "production";

@izadoesdev izadoesdev merged commit 1c29ab2 into staging Jul 8, 2026
19 checks passed
@izadoesdev izadoesdev deleted the izadoesdev/gate-dev-axiom-telemetry branch July 8, 2026 00:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant