Class: Foam::Otel::PayloadCapture::BodyAccumulator

Inherits:
Object
  • Object
show all
Defined in:
lib/foam/otel/payload_capture.rb

Overview

Bounded, defensive accumulation shared by both directions. Tracks a stream POSITION and a HIGH-WATER mark so a reposition + re-read (the Rack::MethodOverride shape: an outer middleware reads the form body, rewinds, and the framework re-reads it) never double-captures or double-counts — only bytes beyond the high-water mark are new. The raw buffer is binary and hard-capped; text is scrubbed to valid UTF-8 and cap-cut only at snapshot time.

Instance Method Summary collapse

Constructor Details

#initialize(capture_text:) ⇒ BodyAccumulator

Returns a new instance of BodyAccumulator.



427
428
429
430
431
# File 'lib/foam/otel/payload_capture.rb', line 427

def initialize(capture_text:)
  @buffer = capture_text ? String.new(encoding: Encoding::BINARY) : nil
  @pos = 0
  @high_water = 0
end

Instance Method Details

#bytesObject

The true byte count observed (the high-water mark: rewound re-reads never inflate it).



471
472
473
# File 'lib/foam/otel/payload_capture.rb', line 471

def bytes
  @high_water
end

#note_position(new_pos) ⇒ Object

A reposition on the underlying stream (rewind / seek / pos=): track the new position so a re-read never double-captures or double-counts. Anything non-sensical degrades to 0 — the rewind-equivalent — which can only UNDER-position (bytes below the high-water mark are never appended twice; rule 9 direction).



461
462
463
# File 'lib/foam/otel/payload_capture.rb', line 461

def note_position(new_pos)
  @pos = new_pos.is_a?(Integer) && new_pos >= 0 ? new_pos : 0
end

#observe(chunk) ⇒ Object



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/foam/otel/payload_capture.rb', line 433

def observe(chunk)
  return unless chunk.is_a?(String)

  size = chunk.bytesize
  return if size.zero?

  new_pos = @pos + size
  if new_pos > @high_water
    # Never more than the chunk itself: a forward seek past the
    # high-water mark makes the positional gap look "fresh", but the
    # skipped bytes were never observed — only the chunk's own tail
    # is appendable (the high-water mark still advances to the true
    # stream position, so re-reads after it stay deduped).
    fresh = [new_pos - @high_water, size].min
    append(chunk.byteslice(size - fresh, fresh))
    @high_water = new_pos
  end
  @pos = new_pos
  nil
rescue StandardError, SystemStackError
  nil
end

#saw_data?Boolean

Returns:

  • (Boolean)


465
466
467
# File 'lib/foam/otel/payload_capture.rb', line 465

def saw_data?
  @high_water.positive?
end

#snapshotObject

The captured text — scrubbed to valid UTF-8, cut at MAX_CAPTURED_BODY_CHARS with the fleet truncation marker — or nil when text capture is off or nothing was seen.



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/foam/otel/payload_capture.rb', line 478

def snapshot
  return nil if @buffer.nil? || !saw_data?

  text = Redaction.scrub_utf8(@buffer.dup)
  return nil unless text.is_a?(String) && !text.empty?

  if text.length > MAX_CAPTURED_BODY_CHARS
    text[0, MAX_CAPTURED_BODY_CHARS] + TRUNCATION_MARKER
  elsif @high_water > @buffer.bytesize
    text + TRUNCATION_MARKER # bytes beyond the buffer cap were observed
  else
    text
  end
rescue StandardError, SystemStackError
  nil
end