Skip to content

Extract the three duplicated OTLP senders; logs drops batches on 5xx where metrics and traces retry #4570

Description

@turnipdabeets

PostHogCoreStateless has three near-identical OTLP senders — _sendLogsBatch, _sendMetricsBatch, and _sendTracesBatch. Each is ~50 lines and they differ only in the endpoint path and auth style. Nothing keeps them in sync, and they have already drifted in a way that loses data.

The drift

All three classify a send failure into the same tagged outcome, but not by the same rule:

sender retry-later when
_sendLogsBatch err instanceof PostHogFetchNetworkError
_sendMetricsBatch isPostHogFetchRetryableError(err)
_sendTracesBatch isPostHogFetchRetryableError(err)

isPostHogFetchRetryableError returns true for HTTP 408, 429, and ≥500, or a network error. PostHogFetchNetworkError is network-only.

So once fetchWithRetry exhausts its internal retries against a struggling ingestion endpoint:

  • logs classifies the 429/5xx as fatal and drops the batch
  • metrics and traces classify it as retry-later and keep the batch queued for the next flush

Logs is the outlier — the two senders written later both use the broader predicate, and _sendMetricsBatch carries a comment explaining the choice. This looks like drift rather than a deliberate decision, but either way the behaviour isn't documented as intentional anywhere.

Why extracting helps

The bodies are otherwise identical: disabled check, JSON.stringify, gzip unless disableCompression, custom headers, fetchWithRetry with a retryCheck that excludes 413, then the same four-way outcome mapping.

A single private _sendOtlpBatch({ path, auth, payload }) returning SendBatchOutcome collapses ~150 lines to ~15 plus three thin wrappers, and makes the retry policy one decision instead of three copies that can silently disagree.

Notes

  • Fixing the logs classification is a behaviour change to a shipped pipeline (batches that are currently dropped would start being retried), so it deserves its own verification rather than riding along silently in a refactor.
  • Purely internal — no public API surface involved.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions