bitfab 0.34.1 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/bitfab/client.rb +4 -0
- data/lib/bitfab/compress.rb +29 -0
- data/lib/bitfab/http_client.rb +4 -1
- data/lib/bitfab/payload_budget.rb +152 -0
- data/lib/bitfab/replay.rb +6 -4
- data/lib/bitfab/replay_branch.rb +59 -9
- data/lib/bitfab/serialize.rb +35 -8
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0b827ad6e9bca08b3025f9c2419290910999a5985705369e6375aee8091232c
|
|
4
|
+
data.tar.gz: e6e0a414013d3bee0efda22415430d9690fe537973a36e5f3dda540fcda3d5e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee72cf5ed5c81ad34dbd4d980091523c90c7cb1d11fc716499ab5f604f1829f5653851350942b2cea6660e20e67c065ae8807e0778153a0ccf08a1397cc08491
|
|
7
|
+
data.tar.gz: 297efa6c3e5d9b1086536dd5e560168e9f9d4c638638b562a67d69ccb1c37253534da6447778032f93e186532f0a563eb7b7f41148eb6b9e0e2ac91c4ed2196e
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -407,6 +407,7 @@ module Bitfab
|
|
|
407
407
|
{
|
|
408
408
|
neon_branch_id: lease["neonBranchId"],
|
|
409
409
|
snapshot_timestamp: lease["snapshotTimestamp"],
|
|
410
|
+
region: lease["region"],
|
|
410
411
|
original_trace_id: replay_ctx[:source_bitfab_trace_id],
|
|
411
412
|
# Deprecated wire alias, kept so this SDK still reports usage
|
|
412
413
|
# against servers that predate the rename.
|
|
@@ -593,6 +594,9 @@ module Bitfab
|
|
|
593
594
|
if db_snapshot_usage[:snapshot_timestamp]
|
|
594
595
|
usage["snapshot_timestamp"] = db_snapshot_usage[:snapshot_timestamp]
|
|
595
596
|
end
|
|
597
|
+
if db_snapshot_usage[:region]
|
|
598
|
+
usage["region"] = db_snapshot_usage[:region]
|
|
599
|
+
end
|
|
596
600
|
if db_snapshot_usage[:original_trace_id]
|
|
597
601
|
usage["original_trace_id"] = db_snapshot_usage[:original_trace_id]
|
|
598
602
|
# Deprecated wire alias, kept so this SDK still reports usage
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
|
|
5
|
+
module Bitfab
|
|
6
|
+
# Request body compression for Bitfab API requests.
|
|
7
|
+
module Compress
|
|
8
|
+
DISABLE_COMPRESSION_ENV = "BITFAB_DISABLE_COMPRESSION"
|
|
9
|
+
|
|
10
|
+
# Below this, compressing costs more than the saved bytes are worth, so
|
|
11
|
+
# small requests (function lookups, replay status polls, single-span
|
|
12
|
+
# batches) ride uncompressed.
|
|
13
|
+
MIN_COMPRESSED_BYTES = 8_192
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Returns the body to send and the Content-Encoding it carries, or nil when
|
|
18
|
+
# the body is sent as-is. Compression is best-effort: any failure sends the
|
|
19
|
+
# original body rather than dropping the span.
|
|
20
|
+
def encode_request_body(body)
|
|
21
|
+
return [body, nil] if ENV[DISABLE_COMPRESSION_ENV]
|
|
22
|
+
return [body, nil] if body.bytesize < MIN_COMPRESSED_BYTES
|
|
23
|
+
|
|
24
|
+
[Zlib.gzip(body), "gzip"]
|
|
25
|
+
rescue
|
|
26
|
+
[body, nil]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/bitfab/http_client.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "net/http"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
|
+
require_relative "compress"
|
|
7
8
|
require_relative "constants"
|
|
8
9
|
require_relative "serialize"
|
|
9
10
|
require_relative "transport"
|
|
@@ -69,7 +70,9 @@ module Bitfab
|
|
|
69
70
|
http.read_timeout = request_timeout
|
|
70
71
|
|
|
71
72
|
req = Net::HTTP::Post.new(uri.path, headers)
|
|
72
|
-
|
|
73
|
+
encoded_body, content_encoding = Compress.encode_request_body(body)
|
|
74
|
+
req["Content-Encoding"] = content_encoding if content_encoding
|
|
75
|
+
req.body = encoded_body
|
|
73
76
|
|
|
74
77
|
response = http.request(req)
|
|
75
78
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
# The ceiling on a span's encoded payload, and the trimming that enforces it.
|
|
5
|
+
#
|
|
6
|
+
# A span's whole payload (input, output, contexts, prompt, metadata) ships as
|
|
7
|
+
# a single bitfab.payload string attribute, and the exporter drops any carrier
|
|
8
|
+
# that exceeds the per-request byte ceiling outright rather than trimming it.
|
|
9
|
+
# Capping each value on its own cannot prevent that: two values that each fit
|
|
10
|
+
# can still add up to an undeliverable span. So the budget is enforced on the
|
|
11
|
+
# encoded payload as a whole, and an oversized span ships with its largest
|
|
12
|
+
# fields stubbed instead of vanishing.
|
|
13
|
+
#
|
|
14
|
+
# The budget is measured on the *carrier* (the payload re-escaped into the
|
|
15
|
+
# OTLP attribute), not on the payload body, because the carrier is what the
|
|
16
|
+
# exporter weighs. Bounding the body instead leaves escape-heavy content to
|
|
17
|
+
# blow the request ceiling anyway: a body of escaped JSON, Windows paths, or
|
|
18
|
+
# regexes is nearly all backslashes, and every one of them doubles. Measured
|
|
19
|
+
# on a body sized exactly to a 2.4 MB cap, prose produced a 2.4 MB carrier but
|
|
20
|
+
# backslash-dense content produced 4.8 MB, which the exporter dropped.
|
|
21
|
+
#
|
|
22
|
+
# 2.8 MB leaves room beneath the 3 MB request ceiling for the span and request
|
|
23
|
+
# envelopes wrapped around the attribute.
|
|
24
|
+
module PayloadBudget
|
|
25
|
+
MAX_SPAN_CARRIER_BYTES = 2_800_000
|
|
26
|
+
|
|
27
|
+
# Span fields that identify the span rather than carry user data. Trimming
|
|
28
|
+
# one would leave a span that no longer says what it is, so they stay
|
|
29
|
+
# whatever the payload costs.
|
|
30
|
+
STRUCTURAL_SPAN_KEYS = ["name", "type", "function_name", "error_source"].freeze
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# The byte length +body+ occupies once re-escaped as a JSON string value.
|
|
35
|
+
#
|
|
36
|
+
# +body+ is itself JSON text, so the first encode already replaced every
|
|
37
|
+
# control character with a \uXXXX sequence. Only " and \ are left to
|
|
38
|
+
# escape, and each costs exactly one more byte, which bounds the expansion
|
|
39
|
+
# at 2x and is what lets .fits_carrier_budget? skip this scan for all but
|
|
40
|
+
# the largest payloads.
|
|
41
|
+
def carrier_byte_length(body)
|
|
42
|
+
body.bytesize + body.count("\"\\\\") + 2
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Whether +body+ fits the carrier budget, measuring exactly only when the
|
|
46
|
+
# cheap bounds cannot already decide it. Escaping never shrinks the body and
|
|
47
|
+
# can at most double it, so anything under half the budget always fits and
|
|
48
|
+
# anything past the budget never does. Ordinary spans settle on the first
|
|
49
|
+
# comparison and never pay for the scan.
|
|
50
|
+
def fits_carrier_budget?(body)
|
|
51
|
+
size = body.bytesize
|
|
52
|
+
return true if size * 2 + 2 <= MAX_SPAN_CARRIER_BYTES
|
|
53
|
+
return false if size + 2 > MAX_SPAN_CARRIER_BYTES
|
|
54
|
+
|
|
55
|
+
carrier_byte_length(body) <= MAX_SPAN_CARRIER_BYTES
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Return a body within the budget, plus the fields that had to be stubbed.
|
|
59
|
+
def enforce(payload, body, &encode)
|
|
60
|
+
return [body, []] if fits_carrier_budget?(body)
|
|
61
|
+
return [body, []] unless payload.is_a?(Hash)
|
|
62
|
+
|
|
63
|
+
trimmed_payload, trimmed = trim(payload, &encode)
|
|
64
|
+
return [body, []] if trimmed_payload.nil?
|
|
65
|
+
|
|
66
|
+
mark_trimmed(trimmed_payload, trimmed)
|
|
67
|
+
[encode.call(trimmed_payload), trimmed]
|
|
68
|
+
rescue
|
|
69
|
+
[body, []]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Stub the largest payload fields until the encoded body fits the budget.
|
|
73
|
+
# Returns [trimmed_payload, trimmed_keys], or nil when nothing could be
|
|
74
|
+
# trimmed: the caller then ships the oversized body and lets the exporter
|
|
75
|
+
# report the drop, which still beats silently emptying a span.
|
|
76
|
+
def trim(payload, &encode)
|
|
77
|
+
copy, containers = clone_trimmable(payload)
|
|
78
|
+
candidates = collect_candidates(containers)
|
|
79
|
+
return nil if candidates.empty?
|
|
80
|
+
|
|
81
|
+
trimmed = []
|
|
82
|
+
candidates.each do |container, key, size|
|
|
83
|
+
container[key] = "<unserializable: too_large_#{size}_bytes>"
|
|
84
|
+
trimmed << key
|
|
85
|
+
body = encode.call(copy)
|
|
86
|
+
return [copy, trimmed] if fits_carrier_budget?(body)
|
|
87
|
+
end
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Copy the records holding user data so trimming never mutates the caller's.
|
|
92
|
+
def clone_trimmable(payload)
|
|
93
|
+
copy = payload.dup
|
|
94
|
+
containers = []
|
|
95
|
+
|
|
96
|
+
span_data = copy["span_data"] || copy[:span_data]
|
|
97
|
+
if span_data.is_a?(Hash)
|
|
98
|
+
clone = span_data.dup
|
|
99
|
+
copy[copy.key?("span_data") ? "span_data" : :span_data] = clone
|
|
100
|
+
containers << clone
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
raw_span = copy["rawSpan"] || copy[:rawSpan]
|
|
104
|
+
raw_span_data = raw_span.is_a?(Hash) ? (raw_span["span_data"] || raw_span[:span_data]) : nil
|
|
105
|
+
if raw_span_data.is_a?(Hash)
|
|
106
|
+
clone = raw_span_data.dup
|
|
107
|
+
raw_span_copy = raw_span.dup
|
|
108
|
+
raw_span_copy[raw_span.key?("span_data") ? "span_data" : :span_data] = clone
|
|
109
|
+
copy[copy.key?("rawSpan") ? "rawSpan" : :rawSpan] = raw_span_copy
|
|
110
|
+
containers << clone
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# No span_data anywhere: a trace-level or otherwise unfamiliar payload.
|
|
114
|
+
# Trim its own fields rather than give up, so an oversized body still
|
|
115
|
+
# ships.
|
|
116
|
+
containers << copy if containers.empty?
|
|
117
|
+
|
|
118
|
+
[copy, containers]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def collect_candidates(containers)
|
|
122
|
+
candidates = containers.flat_map do |container|
|
|
123
|
+
container.filter_map do |key, value|
|
|
124
|
+
next if STRUCTURAL_SPAN_KEYS.include?(key.to_s) || value.nil?
|
|
125
|
+
|
|
126
|
+
begin
|
|
127
|
+
[container, key, JSON.generate(value).bytesize]
|
|
128
|
+
rescue
|
|
129
|
+
next
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
candidates.sort_by { |entry| -entry[2] }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Record the trim in the payload's own errors, which is what the server
|
|
137
|
+
# reads to flag a trace as incomplete.
|
|
138
|
+
def mark_trimmed(payload, trimmed)
|
|
139
|
+
key = payload.key?(:errors) ? :errors : "errors"
|
|
140
|
+
existing = payload[key]
|
|
141
|
+
errors = existing.is_a?(Array) ? existing.dup : []
|
|
142
|
+
errors << {
|
|
143
|
+
"source" => "sdk",
|
|
144
|
+
"step" => "payload_budget",
|
|
145
|
+
"error" => "trimmed oversized field(s) to fit the " \
|
|
146
|
+
"#{MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: " \
|
|
147
|
+
"#{trimmed.uniq.join(", ")}"
|
|
148
|
+
}
|
|
149
|
+
payload[key] = errors
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -62,10 +62,12 @@ module Bitfab
|
|
|
62
62
|
# (via +database_url+). Reported on the trace
|
|
63
63
|
# completion inside the +db_snapshot_usage+ record (its +accessed+
|
|
64
64
|
# field) so the server can distinguish "branch was provisioned and
|
|
65
|
-
# exposed" from "branch URL was actually consumed".
|
|
66
|
-
#
|
|
67
|
-
# (e.g. a process-isolated runner writing an env overlay) must
|
|
68
|
-
#
|
|
65
|
+
# exposed" from "branch URL was actually consumed". Only an explicit
|
|
66
|
+
# +database_url+ read may set it. A path that hands the URL over by other
|
|
67
|
+
# means (e.g. a process-isolated runner writing an env overlay) must
|
|
68
|
+
# leave it alone: setting it there would make every such replay report
|
|
69
|
+
# accessed for free, and the flag would stop separating "branch was
|
|
70
|
+
# used" from "branch was offered".
|
|
69
71
|
ctx[:db_branch_lease] = db_branch_lease if db_branch_lease
|
|
70
72
|
ctx[:source_bitfab_trace_id] = source_bitfab_trace_id if source_bitfab_trace_id
|
|
71
73
|
Thread.current[REPLAY_CONTEXT_KEY] = ctx
|
data/lib/bitfab/replay_branch.rb
CHANGED
|
@@ -17,9 +17,20 @@ module Bitfab
|
|
|
17
17
|
# protocol term). Its useful fields are exposed directly here so customer code
|
|
18
18
|
# never sees the word.
|
|
19
19
|
class ReplayBranch
|
|
20
|
+
# The provider's own id for this branch, e.g. for correlating with its console.
|
|
21
|
+
attr_reader :neon_branch_id
|
|
22
|
+
|
|
23
|
+
# Env var name the customer's app reads, e.g. "DATABASE_URL".
|
|
24
|
+
attr_reader :env_key
|
|
25
|
+
|
|
20
26
|
# When this branch's URL stops being valid. ISO-8601.
|
|
21
27
|
attr_reader :expires_at
|
|
22
28
|
|
|
29
|
+
# The instant this branch is pinned to: the source trace's wall clock, read
|
|
30
|
+
# just before the traced method ran. Compare it against the trace you meant
|
|
31
|
+
# to replay to confirm the branch is the right point in history.
|
|
32
|
+
attr_reader :snapshot_timestamp
|
|
33
|
+
|
|
23
34
|
# Deep link to the branch in the provider console, if available.
|
|
24
35
|
attr_reader :provider_console_url
|
|
25
36
|
|
|
@@ -36,13 +47,28 @@ module Bitfab
|
|
|
36
47
|
|
|
37
48
|
# Built by Bitfab.current_replay_branch; never constructed by callers.
|
|
38
49
|
def initialize(lease, trace_id, context)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
# Copy the lease wholesale minus the connection string, so a field the
|
|
51
|
+
# server starts sending reaches customer code without an SDK release.
|
|
52
|
+
# Everything set here must stay plain data: #database_url is the only
|
|
53
|
+
# member allowed to mark the branch as accessed.
|
|
54
|
+
# The internals are underscore-prefixed because the loop below writes an
|
|
55
|
+
# ivar per lease key: snake_case never emits a leading underscore, so no
|
|
56
|
+
# field the server invents can collide with them. A plain @fields would
|
|
57
|
+
# be clobbered mid-loop by a key named "fields", corrupting #as_json.
|
|
58
|
+
fields = {}
|
|
59
|
+
lease.each do |key, value|
|
|
60
|
+
next if key == "databaseUrl"
|
|
61
|
+
|
|
62
|
+
name = snake_case(key)
|
|
63
|
+
fields[name] = value
|
|
64
|
+
instance_variable_set(:"@#{name}", value)
|
|
65
|
+
define_singleton_method(name) { value } unless respond_to?(name)
|
|
66
|
+
end
|
|
43
67
|
@trace_id = trace_id
|
|
44
|
-
|
|
45
|
-
@
|
|
68
|
+
fields["trace_id"] = trace_id
|
|
69
|
+
@_fields = fields.freeze
|
|
70
|
+
@_url = lease["databaseUrl"]
|
|
71
|
+
@_context = context
|
|
46
72
|
freeze
|
|
47
73
|
end
|
|
48
74
|
|
|
@@ -54,15 +80,39 @@ module Bitfab
|
|
|
54
80
|
# was actually used". The other readers inspect the lease without exposing
|
|
55
81
|
# the connection string, so they deliberately do not record anything.
|
|
56
82
|
def database_url
|
|
57
|
-
@
|
|
58
|
-
@
|
|
83
|
+
@_context[:db_snapshot_accessed] = true
|
|
84
|
+
@_url
|
|
59
85
|
end
|
|
60
86
|
|
|
61
87
|
# Redact the connection string so a logged or inspected branch cannot leak it.
|
|
62
88
|
def inspect
|
|
63
89
|
"#<Bitfab::ReplayBranch trace_id=#{@trace_id.inspect} " \
|
|
64
90
|
"expires_at=#{@expires_at.inspect} region=#{@region.inspect} " \
|
|
65
|
-
"read_only=#{@read_only.inspect}
|
|
91
|
+
"read_only=#{@read_only.inspect} " \
|
|
92
|
+
"snapshot_timestamp=#{@snapshot_timestamp.inspect}>"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ActiveSupport gives every object a +to_json+ that serializes its instance
|
|
96
|
+
# variables, so under Rails a branch reaching a log line or an API response
|
|
97
|
+
# would carry the connection string and the whole replay context. Serialize
|
|
98
|
+
# the exposed fields only, the same set +inspect+ shows.
|
|
99
|
+
def as_json(_options = nil)
|
|
100
|
+
@_fields.dup
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_json(*args)
|
|
104
|
+
as_json.to_json(*args)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Two passes so a run of capitals stays one word: "dbURL" is "db_url", not
|
|
110
|
+
# "db_u_r_l". Must match the Python SDK's conversion exactly, or the same
|
|
111
|
+
# server field would arrive under different names in the two SDKs.
|
|
112
|
+
def snake_case(key)
|
|
113
|
+
key.gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
|
114
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
115
|
+
.downcase
|
|
66
116
|
end
|
|
67
117
|
end
|
|
68
118
|
end
|
data/lib/bitfab/serialize.rb
CHANGED
|
@@ -4,13 +4,17 @@ require "base64"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "time"
|
|
6
6
|
|
|
7
|
+
require_relative "payload_budget"
|
|
8
|
+
|
|
7
9
|
module Bitfab
|
|
8
10
|
module Serialize
|
|
9
|
-
# Cap on serialized
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
11
|
+
# Cap on a single serialized value. Walking arbitrary objects can produce
|
|
12
|
+
# hundreds of KB to MB of useless internal state, so this is the cheap
|
|
13
|
+
# early-out that stops the walk before a pathological object is carried any
|
|
14
|
+
# further. It is deliberately the same number as the whole-span budget: one
|
|
15
|
+
# legitimately large value may use the entire budget, and PayloadBudget is
|
|
16
|
+
# what enforces the total once every field is in.
|
|
17
|
+
MAX_SERIALIZED_BYTES = PayloadBudget::MAX_SPAN_CARRIER_BYTES
|
|
14
18
|
|
|
15
19
|
# Recursion guard for cyclic graphs and pathologically nested structures.
|
|
16
20
|
MAX_SERIALIZE_DEPTH = 16
|
|
@@ -257,7 +261,28 @@ module Bitfab
|
|
|
257
261
|
# the whole span/trace silently. A degraded payload warns loudly so the
|
|
258
262
|
# trace isn't quietly left incomplete or not replayable.
|
|
259
263
|
def safe_generate(payload)
|
|
260
|
-
|
|
264
|
+
# Budget the value that was actually encoded, not the caller's original: a
|
|
265
|
+
# cyclic or otherwise non-encodable field cannot be sized (JSON.generate
|
|
266
|
+
# raises on it), so on the original graph the biggest field is skipped as
|
|
267
|
+
# a trim candidate and the oversized body ships anyway. The sanitized copy
|
|
268
|
+
# has those values already replaced with stubs, so every field is sizeable.
|
|
269
|
+
body, encoded = encode_unbounded(payload)
|
|
270
|
+
body, trimmed = PayloadBudget.enforce(encoded, body) { |value| encode_unbounded(value).first }
|
|
271
|
+
if trimmed.any?
|
|
272
|
+
Bitfab.warn_once(
|
|
273
|
+
"payload-over-budget",
|
|
274
|
+
"a span payload exceeded the #{PayloadBudget::MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its " \
|
|
275
|
+
"largest field(s) (#{trimmed.uniq.join(", ")}) were replaced with placeholders " \
|
|
276
|
+
"so the span still ships. The span is incomplete and may not be replayable."
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
body
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Returns [body, encoded_value] where encoded_value is what the body was
|
|
283
|
+
# generated from: the payload itself, or its sanitized copy.
|
|
284
|
+
def encode_unbounded(payload)
|
|
285
|
+
[JSON.generate(payload), payload]
|
|
261
286
|
rescue => e
|
|
262
287
|
Bitfab.warn_once(
|
|
263
288
|
"request-body-stubbed",
|
|
@@ -268,10 +293,12 @@ module Bitfab
|
|
|
268
293
|
)
|
|
269
294
|
|
|
270
295
|
begin
|
|
271
|
-
|
|
296
|
+
sanitized = sanitize_payload(payload)
|
|
297
|
+
[JSON.generate(sanitized), sanitized]
|
|
272
298
|
rescue
|
|
273
299
|
# Truly pathological. Still never drop silently: send a marker body.
|
|
274
|
-
|
|
300
|
+
marker = {"error" => "payload_serialize_failed"}
|
|
301
|
+
[JSON.generate(marker), marker]
|
|
275
302
|
end
|
|
276
303
|
end
|
|
277
304
|
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "json"
|
|
|
5
5
|
require_relative "bitfab/version"
|
|
6
6
|
require_relative "bitfab/constants"
|
|
7
7
|
require_relative "bitfab/warn_once"
|
|
8
|
+
require_relative "bitfab/payload_budget"
|
|
8
9
|
require_relative "bitfab/serialize"
|
|
9
10
|
require_relative "bitfab/db_snapshot"
|
|
10
11
|
require_relative "bitfab/span_context"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bitfab
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.36.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -138,11 +138,13 @@ files:
|
|
|
138
138
|
- README.md
|
|
139
139
|
- lib/bitfab.rb
|
|
140
140
|
- lib/bitfab/client.rb
|
|
141
|
+
- lib/bitfab/compress.rb
|
|
141
142
|
- lib/bitfab/constants.rb
|
|
142
143
|
- lib/bitfab/db_snapshot.rb
|
|
143
144
|
- lib/bitfab/http_client.rb
|
|
144
145
|
- lib/bitfab/mock_override.rb
|
|
145
146
|
- lib/bitfab/otel.rb
|
|
147
|
+
- lib/bitfab/payload_budget.rb
|
|
146
148
|
- lib/bitfab/replay.rb
|
|
147
149
|
- lib/bitfab/replay_branch.rb
|
|
148
150
|
- lib/bitfab/serialize.rb
|