Class: Restless::Uploader

Inherits:
Object
  • Object
show all
Defined in:
lib/restless/uploader.rb

Overview

CONTRACT.md sections 8 (wire format) and 9 (batching).

Never raises to callers. Upload failures go to stderr under the debug flag and are otherwise swallowed: observability must not break the request path (SAFETY-004, SAFETY-008).

Constant Summary collapse

BATCH_SIZE =

BATCH-001

10
FLUSH_INTERVAL_MS =

BATCH-002

5000
MAX_QUEUE =

BATCH-004

1000
HTTP_TIMEOUT_S =
10

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, base_url:, request_id_prefix: nil, on_response: nil, transport: nil) ⇒ Uploader

Returns a new instance of Uploader.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/restless/uploader.rb', line 25

def initialize(api_key:, base_url:, request_id_prefix: nil, on_response: nil,
               transport: nil)
  @api_key = api_key.to_s
  @base_url = base_url.to_s
  @request_id_prefix = request_id_prefix
  @on_response = on_response
  # Test hook. Anything responding to
  # `call(url, headers, body) -> [status, body]`.
  @transport = transport

  @queue = []
  @mutex = Mutex.new
  @timer = nil
  @inflight = []

  warn_if_insecure
end

Instance Attribute Details

#base_urlObject (readonly)

Returns the value of attribute base_url.



23
24
25
# File 'lib/restless/uploader.rb', line 23

def base_url
  @base_url
end

#request_id_prefixObject (readonly)

Returns the value of attribute request_id_prefix.



23
24
25
# File 'lib/restless/uploader.rb', line 23

def request_id_prefix
  @request_id_prefix
end

Instance Method Details

#api_key?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/restless/uploader.rb', line 43

def api_key?
  !@api_key.empty?
end

#flushObject

BATCH-005, BATCH-006, BATCH-007. Synchronous: resolves when the attempt completes.



88
89
90
91
92
93
# File 'lib/restless/uploader.rb', line 88

def flush
  batch = @mutex.synchronize { take_batch }
  upload(batch) unless batch.nil? || batch.empty?
  join_inflight
  nil
end

#optionsObject



95
96
97
98
99
100
101
# File 'lib/restless/uploader.rb', line 95

def options
  {
    base_url: @base_url,
    request_id_prefix: @request_id_prefix,
    has_api_key: api_key?
  }
end

#push(captured) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/restless/uploader.rb', line 61

def push(captured)
  # BATCH-008.
  return if Env.test_run? && !Env.setup_mode?

  batch = nil
  @mutex.synchronize do
    if @queue.length >= MAX_QUEUE
      # Drop the OLDEST. The newest entries are the ones an operator is
      # actively debugging.
      @queue.shift
      Env.debug_log("queue at #{MAX_QUEUE} -- dropping oldest captured request")
    end
    @queue << captured

    if flush_immediately? || @queue.length >= BATCH_SIZE
      batch = take_batch
    else
      start_timer
    end
  end

  upload_async(batch) if batch
  nil
end

#warn_if_insecureObject

WIRE-006. The project key and every captured header would otherwise ship in the clear with no signal anywhere.



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/restless/uploader.rb', line 49

def warn_if_insecure
  uri = URI.parse(@base_url)
  return unless uri.scheme == "http"
  return if %w[localhost 127.0.0.1].include?(uri.host)

  warn("[restless-sdk] RESTLESS_BASE_URL=#{@base_url} is plain HTTP -- your API " \
       "key and every captured header will be transmitted unencrypted. " \
       "Use https:// or localhost.")
rescue StandardError
  nil
end