Skip to content

Unified IoAction error type + HTTP response classifier #1304

Closed

Description

@strawgate

Problem

Each I/O component reinvents error classification:

  • OTLP sink returns SendResult (4 variants)
  • File tailer returns io::Result
  • Receivers return InputError (3 variants)
  • All 3 HTTP sinks (OTLP, Elasticsearch, Loki) duplicate identical status code → result classification (~300 lines total): 429 → RetryAfter, 413 → Rejected, 5xx → IoError, Retry-After header parsing

Solution

1. Unified error type

pub enum IoAction {
    Retry { source: io::Error, after: Option<Duration> },
    Reject { reason: String },
    Fatal { source: io::Error },
}

2. Shared HTTP response classifier

Extract into logfwd-output/src/http_classify.rs:

pub fn classify_http_response(response: &reqwest::Response) -> SendResult {
    let status = response.status();
    if status.is_success() { return SendResult::Ok; }
    if status == StatusCode::TOO_MANY_REQUESTS {
        let retry_after = parse_retry_after(response.headers());
        return SendResult::RetryAfter(retry_after);
    }
    if status == StatusCode::PAYLOAD_TOO_LARGE { return SendResult::Rejected(...); }
    if status.is_server_error() { return SendResult::IoError(...); }
    SendResult::Rejected(...)
}

Scope

  • Design IoAction enum in logfwd-types (or logfwd-output)
  • Extract classify_http_response() helper
  • Extract parse_retry_after() helper
  • Update OTLP sink to use shared classifier
  • Update Elasticsearch sink to use shared classifier
  • Update Loki sink to use shared classifier

Evidence

~21 instances of duplicated status code classification across 3 sinks. Deduplicating saves ~300 lines and makes adding new HTTP sinks trivial.

Relates to #1283 (I/O refactor research), #1302 (semantic fields)

Activity

  1. added
    enhancementNew feature or request
    P2: mediumMinor bug, missing validation, metric inaccuracy
    architectureSystemic design improvements that eliminate classes of bugs
    on Apr 6, 2026
  2. github-actions commented on Apr 6, 2026

    @github-actions
    Contributor

    tl;dr: Outcome B — needs human decision. I found clear duplication in HTTP status classification, but there’s a blocking semantic conflict: this issue proposes 5xx -> IoError, while open PR #1297 changes OTLP 5xx -> RetryAfter, and the current OTLP test suite still asserts error-on-5xx.

    Recommendation

    Please decide one policy before implementation:

    1. Policy A (issue text): 5xx -> IoError (worker uses exponential retry path)
    2. Policy B (PR fix(otlp_sink): return RetryAfter on 5xx server errors #1297 direction): 5xx -> RetryAfter(default/parsed) (worker uses server-directed retry path)

    I recommend Policy B because it aligns server overload handling with 429 semantics and reduces retry-policy divergence across sinks.

    Please add label needs-decision to track this gate.

    Findings
    • Current shared sink result type is SendResult (Ok | IoError | RetryAfter | Rejected):
      • crates/logfwd-output/src/sink.rs:26
    • Worker behavior depends on variant semantics (RetryAfter vs IoError):
      • crates/logfwd/src/worker_pool.rs:559
    • Duplicated HTTP classification in 3 sinks:
      • OTLP: crates/logfwd-output/src/otlp_sink.rs:297
      • Elasticsearch (buffered): crates/logfwd-output/src/elasticsearch.rs:419
      • Elasticsearch (streaming): crates/logfwd-output/src/elasticsearch.rs:577
      • Loki: crates/logfwd-output/src/loki.rs:304
    • Existing OTLP test currently locks in 5xx as error:
      • crates/logfwd-output/src/otlp_sink.rs:848 (send_payload_returns_err_on_5xx_no_loop)
    • ES has sink-specific 413 split behavior that must remain:
      • 413 mapping to InvalidInput in do_send: crates/logfwd-output/src/elasticsearch.rs:432
      • send_batch maps InvalidInput/InvalidData to Rejected: crates/logfwd-output/src/elasticsearch.rs:632
    • No existing IoAction type in codebase (rg "enum IoAction" crates/**/*.rs => no match).
    Verification

    I ran targeted existing tests in this area:

    cargo test -p logfwd-output parse_bulk_item_error_maps_to_rejected_not_io_error -- --nocapture
    cargo test -p logfwd-output test_send_batch_oversized_single_row -- --nocapture

    Observed result: both tests passed.

    Detailed action plan (mechanical once decision is made)
    1. Add shared classifier module
      • Create crates/logfwd-output/src/http_classify.rs.
      • Add:
        • parse_retry_after(headers: &reqwest::header::HeaderMap, default_secs: u64) -> Duration
        • classify_http_status(status: StatusCode, headers: &HeaderMap, default_retry_secs: u64) -> ...
    2. Wire module export
      • Update crates/logfwd-output/src/lib.rs to include mod http_classify;.
    3. Replace duplicated branches
      • OTLP: replace inline status handling at crates/logfwd-output/src/otlp_sink.rs:297-330 with helper.
      • Elasticsearch buffered+streaming: replace status blocks at crates/logfwd-output/src/elasticsearch.rs:419-450 and 577-607.
      • Loki: replace status block at crates/logfwd-output/src/loki.rs:304-327.
    4. Introduce IoAction type (minimal, non-breaking migration)
      • Add IoAction enum in logfwd-types (or logfwd-output if you choose local-first rollout).
      • Keep SendResult at sink boundary initially; add explicit conversion helpers (IoAction <-> SendResult) to avoid worker/input churn in same PR.
    5. Tests to add/update
      • New unit tests in http_classify.rs: 200, 429(+header parse/fallback), 413, generic 4xx, 5xx.
      • OTLP test rename/update (send_payload_returns_err_on_5xx_no_loop) according to decision.
      • New Loki HTTP classification tests (currently missing).
      • ES tests for both buffered and streaming classification parity.
    6. Verification command set
      • cargo test -p logfwd-output
      • cargo test -p logfwd
      • cargo fmt --all && cargo clippy --all-targets -- -D warnings
    Related items
    Type Link Relevance
    Issue #1304 This triage target
    Issue #1283 I/O refactor umbrella; mentions unified error classification direction
    Issue #1302 Parallel logfwd-types foundational work; possible coordination for type placement
    PR #1297 Direct overlap in OTLP 5xx/Retry-After semantics
    PR #1290 OTLP contract tests touching nearby sink/receiver behavior
    File crates/logfwd-output/src/sink.rs:26 Current SendResult contract
    File crates/logfwd/src/worker_pool.rs:559 Retry semantics tied to result variants
    File crates/logfwd-output/src/otlp_sink.rs:297 Duplicated HTTP classification + conflicting 5xx behavior
    File crates/logfwd-output/src/elasticsearch.rs:419 Duplicated classifier + 413 split semantics
    File crates/logfwd-output/src/loki.rs:304 Duplicated classifier

    Note

    🔒 Integrity filtering filtered 8 items

    Integrity filtering activated and filtered the following items during workflow execution.
    This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.


    What is this? | From workflow: Issue Triage

    Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

  3. added 2 commits that reference this issue on Apr 6, 2026
    ef9ed1d
    0bb08f2
  4. strawgate commented on Apr 6, 2026

    @strawgate
    OwnerAuthor

    Closed-state cleanup pass on April 6, 2026: this issue was effectively resolved as part of the Phase 0 I/O refactor in PR #1334.

    The body still captures the original design intent, but it should not be read as live planned work anymore. The remaining open follow-up in this area is the broader sink retry/classification cleanup tracked elsewhere, not this specific foundational extraction step.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2: mediumMinor bug, missing validation, metric inaccuracyarchitectureSystemic design improvements that eliminate classes of bugsenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions