bitfab 0.33.6 → 0.34.0
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/README.md +11 -36
- data/lib/bitfab/http_client.rb +9 -4
- data/lib/bitfab/otel.rb +56 -144
- data/lib/bitfab/transport.rb +2 -2
- data/lib/bitfab/version.rb +1 -1
- metadata +1 -16
- data/lib/bitfab/otel_collector.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 897fb65140b86333bb5012746e570c785560c7865cd79da1e872e4ec567fada2
|
|
4
|
+
data.tar.gz: 26b89a73668a0f86746a349c8f57fe39317f069dbab04dd8731ff91950d24a49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb837f5a94d68391c0b25ac1bc073cba636816270abd623b825b59a2a3d8f387ac67a49aaef93aebcc3201ad24dbe4ef9f4d4738955d5db5387181eb1f94ee68
|
|
7
|
+
data.tar.gz: 0b609be82dca49043083bf966b52b01466f8252abe15b243e4d2ac6a3ea06d5238fb5fdf7b88e7a2c1be22fa4c36b06425baa2261a62ffed6967b5e8c86a8150
|
data/README.md
CHANGED
|
@@ -20,7 +20,6 @@ gem install bitfab
|
|
|
20
20
|
|
|
21
21
|
- Ruby >= 3.4
|
|
22
22
|
- Runtime dependencies: `base64`, `opentelemetry-sdk` (`>= 1.2, < 2`, verified against 1.2 through 1.13, so an application already pinning its own OpenTelemetry version does not have to move)
|
|
23
|
-
- Optional: `opentelemetry-exporter-otlp`, only to deliver through an OpenTelemetry Collector. Use a version compatible with your `opentelemetry-sdk`; if the gem cannot load, the SDK warns once and keeps delivering directly to Bitfab
|
|
24
23
|
|
|
25
24
|
## Quick Start
|
|
26
25
|
|
|
@@ -391,41 +390,17 @@ worker, export scheduling, flush, and shutdown lifecycle. Pipelines are created
|
|
|
391
390
|
trace send and are never installed globally, so an unused or disabled client starts no OTel worker
|
|
392
391
|
and the SDK does not replace an application's own OpenTelemetry setup.
|
|
393
392
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
a positive integer no greater than `3000000` for a Collector or proxy with a stricter limit.
|
|
406
|
-
Invalid values warn once and fall back to the defaults. If Bitfab accepts only part of a batch, it
|
|
407
|
-
returns the standard OTLP `partialSuccess` response and the SDK warns with the rejected-span count
|
|
408
|
-
and reason.
|
|
409
|
-
|
|
410
|
-
```yaml
|
|
411
|
-
receivers:
|
|
412
|
-
otlp:
|
|
413
|
-
protocols:
|
|
414
|
-
http:
|
|
415
|
-
|
|
416
|
-
exporters:
|
|
417
|
-
otlphttp/bitfab:
|
|
418
|
-
endpoint: https://bitfab.ai/api/sdk/otel
|
|
419
|
-
encoding: json
|
|
420
|
-
headers:
|
|
421
|
-
Authorization: Bearer ${env:BITFAB_API_KEY}
|
|
422
|
-
|
|
423
|
-
service:
|
|
424
|
-
pipelines:
|
|
425
|
-
traces:
|
|
426
|
-
receivers: [otlp]
|
|
427
|
-
exporters: [otlphttp/bitfab]
|
|
428
|
-
```
|
|
393
|
+
Requests carry at most eight carriers each and up to 32 run concurrently; set
|
|
394
|
+
`BITFAB_OTEL_EXPORT_CONCURRENCY` to an integer from `1` through `64` to tune that. Requests are
|
|
395
|
+
partitioned at roughly 3 MB; set `BITFAB_OTEL_MAX_REQUEST_BYTES` to a positive integer no greater
|
|
396
|
+
than `3000000` for a proxy with a stricter limit. Invalid values warn once and fall back to the
|
|
397
|
+
defaults. Each carrier is encoded once and the request body is assembled from those encodings, so a
|
|
398
|
+
batch is never re-encoded to measure its size. If Bitfab accepts only part of a batch, it returns
|
|
399
|
+
the standard OTLP `partialSuccess` response and the SDK warns with the rejected-span count and
|
|
400
|
+
reason.
|
|
401
|
+
|
|
402
|
+
Before finalizing a replay, the SDK flushes OTel and polls Bitfab's replay-status API until every
|
|
403
|
+
expected trace completion and span count is persisted.
|
|
429
404
|
|
|
430
405
|
See the [OpenTelemetry Transport Architecture](https://docs.bitfab.ai/otel-architecture) for the
|
|
431
406
|
full component ownership, carrier format, replay barrier, batching, and lifecycle design.
|
data/lib/bitfab/http_client.rb
CHANGED
|
@@ -51,6 +51,12 @@ module Bitfab
|
|
|
51
51
|
# Make a POST request to the Bitfab API.
|
|
52
52
|
# Returns parsed JSON response hash.
|
|
53
53
|
def request(endpoint, payload, timeout: nil, max_retries: 1, retry_delay: 0.1)
|
|
54
|
+
send_encoded(endpoint, Serialize.safe_generate(payload), timeout:, max_retries:, retry_delay:)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# POST an already-encoded body. The span transport encodes its own batches,
|
|
58
|
+
# so routing them back through #request would encode the same data twice.
|
|
59
|
+
def send_encoded(endpoint, body, timeout: nil, max_retries: 1, retry_delay: 0.1)
|
|
54
60
|
uri = URI("#{@service_url}#{endpoint}")
|
|
55
61
|
request_timeout = timeout || @timeout
|
|
56
62
|
|
|
@@ -63,7 +69,7 @@ module Bitfab
|
|
|
63
69
|
http.read_timeout = request_timeout
|
|
64
70
|
|
|
65
71
|
req = Net::HTTP::Post.new(uri.path, headers)
|
|
66
|
-
req.body =
|
|
72
|
+
req.body = body
|
|
67
73
|
|
|
68
74
|
response = http.request(req)
|
|
69
75
|
|
|
@@ -260,14 +266,13 @@ module Bitfab
|
|
|
260
266
|
end
|
|
261
267
|
|
|
262
268
|
@transport ||= Transport.create_trace_transport(
|
|
263
|
-
api_key: @api_key,
|
|
264
269
|
direct_sender: method(:send_transport_request)
|
|
265
270
|
)
|
|
266
271
|
end
|
|
267
272
|
end
|
|
268
273
|
|
|
269
|
-
def send_transport_request(endpoint,
|
|
270
|
-
|
|
274
|
+
def send_transport_request(endpoint, body, timeout)
|
|
275
|
+
send_encoded(endpoint, body, timeout:, max_retries: 1)
|
|
271
276
|
end
|
|
272
277
|
|
|
273
278
|
# Normalize each entry to a hash with stable string keys, accepting either
|
data/lib/bitfab/otel.rb
CHANGED
|
@@ -13,20 +13,17 @@ module Bitfab
|
|
|
13
13
|
# OpenTelemetry delivery for Bitfab spans and trace completions.
|
|
14
14
|
#
|
|
15
15
|
# Bitfab payloads ride as OTLP span attributes (bitfab.operation +
|
|
16
|
-
# bitfab.payload) so a batch
|
|
17
|
-
# OTLP/JSON or through a customer's OTel Collector as protobuf, without the
|
|
16
|
+
# bitfab.payload), so a batch travels to Bitfab as OTLP/JSON without the
|
|
18
17
|
# payload itself changing shape.
|
|
19
18
|
module Otel
|
|
20
19
|
OPERATION_ATTRIBUTE = "bitfab.operation"
|
|
21
20
|
PAYLOAD_ATTRIBUTE = "bitfab.payload"
|
|
22
21
|
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces"
|
|
23
|
-
COLLECTOR_ENDPOINT_ENV = "BITFAB_OTEL_EXPORTER_ENDPOINT"
|
|
24
22
|
MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES"
|
|
25
23
|
EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY"
|
|
26
24
|
MAX_EXPORT_REQUEST_BYTES = 3_000_000
|
|
27
25
|
MAX_QUEUE_SIZE = 8_192
|
|
28
26
|
DIRECT_MAX_EXPORT_BATCH_SIZE = 512
|
|
29
|
-
COLLECTOR_MAX_EXPORT_BATCH_SIZE = 32
|
|
30
27
|
DIRECT_MAX_REQUEST_BATCH_SIZE = 8
|
|
31
28
|
DEFAULT_EXPORT_CONCURRENCY = 32
|
|
32
29
|
MAX_EXPORT_CONCURRENCY = 64
|
|
@@ -35,9 +32,25 @@ module Bitfab
|
|
|
35
32
|
EXPORT_RETRIES = 3
|
|
36
33
|
RETRY_DELAY_SECONDS = 0.1
|
|
37
34
|
|
|
35
|
+
# The comma that joins adjacent spans in the request's span list.
|
|
36
|
+
SPAN_SEPARATOR_BYTES = 1
|
|
37
|
+
|
|
38
38
|
SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
39
39
|
FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
|
|
40
40
|
|
|
41
|
+
# A span encoded exactly as it goes on the wire, carrying its byte count.
|
|
42
|
+
# Encoding once and remembering the size is what keeps request packing
|
|
43
|
+
# linear: sizing a candidate batch by re-encoding the whole request
|
|
44
|
+
# re-escapes every carrier's bitfab.payload string on every span considered.
|
|
45
|
+
EncodedSpan = Struct.new(:encoded, :size)
|
|
46
|
+
|
|
47
|
+
# The invariant head and tail of an OTLP request for one export window. Key
|
|
48
|
+
# order matches what JSON.generate emits for the equivalent Hash, so a body
|
|
49
|
+
# assembled by concatenation is byte-identical to encoding that Hash.
|
|
50
|
+
RequestEnvelope = Struct.new(:head, :tail, :size)
|
|
51
|
+
|
|
52
|
+
RequestBatch = Struct.new(:spans, :size)
|
|
53
|
+
|
|
41
54
|
SPAN_KINDS = {
|
|
42
55
|
internal: 1,
|
|
43
56
|
server: 2,
|
|
@@ -82,11 +95,9 @@ module Bitfab
|
|
|
82
95
|
DEFAULT_EXPORT_CONCURRENCY
|
|
83
96
|
end
|
|
84
97
|
|
|
85
|
-
def create_transport(
|
|
98
|
+
def create_transport(direct_sender:)
|
|
86
99
|
BatchTransport.new(
|
|
87
|
-
api_key:,
|
|
88
100
|
direct_sender:,
|
|
89
|
-
collector_endpoint: collector_endpoint_from_env,
|
|
90
101
|
export_concurrency: export_concurrency_from_env,
|
|
91
102
|
max_request_bytes: max_request_bytes_from_env
|
|
92
103
|
)
|
|
@@ -177,11 +188,6 @@ module Bitfab
|
|
|
177
188
|
|
|
178
189
|
private
|
|
179
190
|
|
|
180
|
-
def collector_endpoint_from_env
|
|
181
|
-
endpoint = ENV[COLLECTOR_ENDPOINT_ENV]
|
|
182
|
-
(endpoint.nil? || endpoint.empty?) ? nil : endpoint
|
|
183
|
-
end
|
|
184
|
-
|
|
185
191
|
def record_span_submission(source_trace_id, payload)
|
|
186
192
|
raw_span = payload["rawSpan"]
|
|
187
193
|
span_id = raw_span["id"] if raw_span.is_a?(Hash)
|
|
@@ -237,7 +243,8 @@ module Bitfab
|
|
|
237
243
|
return SUCCESS if spans.empty?
|
|
238
244
|
|
|
239
245
|
begin
|
|
240
|
-
encoded = spans.map { |span| Encoder.
|
|
246
|
+
encoded = spans.map { |span| Encoder.encode_span(span) }
|
|
247
|
+
envelope = Encoder.request_envelope(spans.first)
|
|
241
248
|
rescue => e
|
|
242
249
|
Bitfab.warn_once(
|
|
243
250
|
"otel-encode-failed",
|
|
@@ -246,8 +253,8 @@ module Bitfab
|
|
|
246
253
|
return FAILURE
|
|
247
254
|
end
|
|
248
255
|
|
|
249
|
-
batches = build_request_batches(
|
|
250
|
-
results = send_batches(
|
|
256
|
+
batches = build_request_batches(envelope, encoded)
|
|
257
|
+
results = send_batches(envelope, batches)
|
|
251
258
|
results.all? ? SUCCESS : FAILURE
|
|
252
259
|
end
|
|
253
260
|
|
|
@@ -261,7 +268,7 @@ module Bitfab
|
|
|
261
268
|
|
|
262
269
|
private
|
|
263
270
|
|
|
264
|
-
def send_batches(
|
|
271
|
+
def send_batches(envelope, batches)
|
|
265
272
|
results = Array.new(batches.size, false)
|
|
266
273
|
next_index = 0
|
|
267
274
|
index_mutex = Mutex.new
|
|
@@ -277,7 +284,7 @@ module Bitfab
|
|
|
277
284
|
end
|
|
278
285
|
break if index >= batches.size
|
|
279
286
|
|
|
280
|
-
results[index] = send_batch(
|
|
287
|
+
results[index] = send_batch(envelope, batches[index])
|
|
281
288
|
end
|
|
282
289
|
end
|
|
283
290
|
end
|
|
@@ -285,40 +292,37 @@ module Bitfab
|
|
|
285
292
|
results
|
|
286
293
|
end
|
|
287
294
|
|
|
288
|
-
def build_request_batches(
|
|
295
|
+
def build_request_batches(envelope, encoded)
|
|
289
296
|
batches = []
|
|
290
297
|
current = []
|
|
298
|
+
size = envelope.size
|
|
291
299
|
|
|
292
300
|
encoded.each do |span|
|
|
293
|
-
|
|
294
|
-
|
|
301
|
+
addition = span.size + (current.empty? ? 0 : SPAN_SEPARATOR_BYTES)
|
|
302
|
+
if !current.empty? && (current.size >= @max_request_batch_size || size + addition > @max_request_bytes)
|
|
303
|
+
batches << RequestBatch.new(current, size)
|
|
295
304
|
current = []
|
|
305
|
+
size = envelope.size
|
|
306
|
+
addition = span.size
|
|
296
307
|
end
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if !current.empty? && Encoder.encoded_size(Encoder.build_request(first, candidate)) > @max_request_bytes
|
|
300
|
-
batches << current
|
|
301
|
-
current = [span]
|
|
302
|
-
else
|
|
303
|
-
current = candidate
|
|
304
|
-
end
|
|
308
|
+
current << span
|
|
309
|
+
size += addition
|
|
305
310
|
end
|
|
306
311
|
|
|
307
|
-
batches << current unless current.empty?
|
|
312
|
+
batches << RequestBatch.new(current, size) unless current.empty?
|
|
308
313
|
batches
|
|
309
314
|
end
|
|
310
315
|
|
|
311
|
-
def send_batch(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
warn_oversized(spans.size)
|
|
316
|
+
def send_batch(envelope, batch)
|
|
317
|
+
if batch.size > @max_request_bytes
|
|
318
|
+
warn_oversized(batch.spans.size)
|
|
315
319
|
return false
|
|
316
320
|
end
|
|
317
321
|
|
|
318
|
-
send_with_retries(
|
|
322
|
+
send_with_retries(Encoder.encode_request(envelope, batch.spans))
|
|
319
323
|
true
|
|
320
324
|
rescue PayloadTooLargeError
|
|
321
|
-
warn_oversized(spans.size)
|
|
325
|
+
warn_oversized(batch.spans.size)
|
|
322
326
|
false
|
|
323
327
|
rescue PartialSuccessError
|
|
324
328
|
false
|
|
@@ -339,11 +343,11 @@ module Bitfab
|
|
|
339
343
|
)
|
|
340
344
|
end
|
|
341
345
|
|
|
342
|
-
def send_with_retries(
|
|
346
|
+
def send_with_retries(body)
|
|
343
347
|
attempt = 0
|
|
344
348
|
begin
|
|
345
349
|
attempt += 1
|
|
346
|
-
response = @direct_sender.call(OTLP_TRACES_ENDPOINT,
|
|
350
|
+
response = @direct_sender.call(OTLP_TRACES_ENDPOINT, body, EXPORT_TIMEOUT_MILLIS / 1000.0)
|
|
347
351
|
check_partial_success(response)
|
|
348
352
|
rescue PartialSuccessError
|
|
349
353
|
raise
|
|
@@ -386,72 +390,6 @@ module Bitfab
|
|
|
386
390
|
end
|
|
387
391
|
end
|
|
388
392
|
|
|
389
|
-
# Splits an export into requests no larger than the configured byte target
|
|
390
|
-
# before handing them to the wrapped Collector exporter.
|
|
391
|
-
class SizeLimitedExporter
|
|
392
|
-
def initialize(exporter, max_request_bytes)
|
|
393
|
-
@exporter = exporter
|
|
394
|
-
@max_request_bytes = max_request_bytes
|
|
395
|
-
# The OTLP gem exposes no public way to measure an encoded request, so
|
|
396
|
-
# requests are partitioned by count alone when its encoder is missing.
|
|
397
|
-
@measurable = exporter.respond_to?(:encode, true)
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
def export(spans, timeout: nil)
|
|
401
|
-
batches = partition(spans.to_a)
|
|
402
|
-
batches.reduce(SUCCESS) do |result, batch|
|
|
403
|
-
exported = @exporter.export(batch, timeout:)
|
|
404
|
-
(exported == SUCCESS) ? result : FAILURE
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
def force_flush(timeout: nil)
|
|
409
|
-
@exporter.force_flush(timeout:)
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
def shutdown(timeout: nil)
|
|
413
|
-
@exporter.shutdown(timeout:)
|
|
414
|
-
end
|
|
415
|
-
|
|
416
|
-
private
|
|
417
|
-
|
|
418
|
-
def partition(spans)
|
|
419
|
-
return [spans] if !@measurable || spans.empty?
|
|
420
|
-
|
|
421
|
-
batches = []
|
|
422
|
-
current = []
|
|
423
|
-
current_size = 0
|
|
424
|
-
|
|
425
|
-
spans.each do |span|
|
|
426
|
-
size = span_bytes(span)
|
|
427
|
-
return [spans] if size.nil?
|
|
428
|
-
|
|
429
|
-
if !current.empty? && current_size + size > @max_request_bytes
|
|
430
|
-
batches << current
|
|
431
|
-
current = []
|
|
432
|
-
current_size = 0
|
|
433
|
-
end
|
|
434
|
-
current << span
|
|
435
|
-
current_size += size
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
batches << current unless current.empty?
|
|
439
|
-
batches
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
# The OTLP gem's encoder returns a serialized String on current versions
|
|
443
|
-
# and a protobuf message on older ones, and returns nil when it cannot
|
|
444
|
-
# encode a span at all. Anything unmeasurable falls back to count-based
|
|
445
|
-
# partitioning rather than failing the export from here.
|
|
446
|
-
def span_bytes(span)
|
|
447
|
-
encoded = @exporter.send(:encode, [span])
|
|
448
|
-
return encoded.bytesize if encoded.respond_to?(:bytesize)
|
|
449
|
-
return encoded.to_proto.bytesize if encoded.respond_to?(:to_proto)
|
|
450
|
-
|
|
451
|
-
nil
|
|
452
|
-
end
|
|
453
|
-
end
|
|
454
|
-
|
|
455
393
|
# Records failed exports so a flush can report delivery, not just drain.
|
|
456
394
|
class DeliveryTrackingExporter
|
|
457
395
|
def initialize(exporter)
|
|
@@ -494,15 +432,13 @@ module Bitfab
|
|
|
494
432
|
class BatchTransport
|
|
495
433
|
attr_reader :owner_pid
|
|
496
434
|
|
|
497
|
-
def initialize(
|
|
435
|
+
def initialize(direct_sender:, max_export_batch_size: nil,
|
|
498
436
|
max_request_batch_size: DIRECT_MAX_REQUEST_BATCH_SIZE, max_queue_size: MAX_QUEUE_SIZE,
|
|
499
437
|
export_concurrency: DEFAULT_EXPORT_CONCURRENCY, max_request_bytes: nil)
|
|
500
438
|
raise ArgumentError, "max_request_batch_size must be a positive integer" unless max_request_batch_size.positive?
|
|
501
439
|
|
|
502
|
-
@api_key = api_key
|
|
503
440
|
@direct_sender = direct_sender
|
|
504
|
-
@
|
|
505
|
-
@max_export_batch_size = max_export_batch_size || default_export_batch_size
|
|
441
|
+
@max_export_batch_size = max_export_batch_size || DIRECT_MAX_EXPORT_BATCH_SIZE
|
|
506
442
|
@max_request_batch_size = max_request_batch_size
|
|
507
443
|
@max_queue_size = max_queue_size
|
|
508
444
|
@export_concurrency = export_concurrency
|
|
@@ -620,10 +556,6 @@ module Bitfab
|
|
|
620
556
|
nil
|
|
621
557
|
end
|
|
622
558
|
|
|
623
|
-
def default_export_batch_size
|
|
624
|
-
@collector_endpoint.nil? ? DIRECT_MAX_EXPORT_BATCH_SIZE : COLLECTOR_MAX_EXPORT_BATCH_SIZE
|
|
625
|
-
end
|
|
626
|
-
|
|
627
559
|
# A forked child inherits the parent's provider and processor but none of
|
|
628
560
|
# its threads, so the pipeline is rebuilt on the first submit after a fork.
|
|
629
561
|
# Callers hold @state_mutex, so exactly one thread rebuilds.
|
|
@@ -667,27 +599,6 @@ module Bitfab
|
|
|
667
599
|
end
|
|
668
600
|
|
|
669
601
|
def create_exporter
|
|
670
|
-
return direct_exporter if @collector_endpoint.nil?
|
|
671
|
-
|
|
672
|
-
require_relative "otel_collector"
|
|
673
|
-
SizeLimitedExporter.new(
|
|
674
|
-
CollectorExporter.new(
|
|
675
|
-
api_key: @api_key,
|
|
676
|
-
endpoint: CollectorExporter.traces_endpoint(@collector_endpoint),
|
|
677
|
-
timeout: EXPORT_TIMEOUT_MILLIS / 1000.0
|
|
678
|
-
),
|
|
679
|
-
@max_request_bytes
|
|
680
|
-
)
|
|
681
|
-
rescue LoadError
|
|
682
|
-
Bitfab.warn_once(
|
|
683
|
-
"otel-collector-gem-missing",
|
|
684
|
-
"#{COLLECTOR_ENDPOINT_ENV} is set but the opentelemetry-exporter-otlp " \
|
|
685
|
-
"gem is not installed; sending spans directly to Bitfab instead"
|
|
686
|
-
)
|
|
687
|
-
direct_exporter
|
|
688
|
-
end
|
|
689
|
-
|
|
690
|
-
def direct_exporter
|
|
691
602
|
DirectExporter.new(
|
|
692
603
|
direct_sender: @direct_sender,
|
|
693
604
|
max_request_bytes: @max_request_bytes,
|
|
@@ -700,21 +611,22 @@ module Bitfab
|
|
|
700
611
|
# Turns finished carrier spans into the OTLP/JSON shapes Bitfab ingests.
|
|
701
612
|
module Encoder
|
|
702
613
|
class << self
|
|
703
|
-
def
|
|
614
|
+
def encode_span(span)
|
|
615
|
+
encoded = JSON.generate(span_to_otlp(span))
|
|
616
|
+
EncodedSpan.new(encoded, encoded.bytesize)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def request_envelope(first)
|
|
704
620
|
scope = first.instrumentation_scope
|
|
705
|
-
{
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
"spans" => spans
|
|
711
|
-
}]
|
|
712
|
-
}]
|
|
713
|
-
}
|
|
621
|
+
resource = JSON.generate({"attributes" => attributes(first.resource.attribute_enumerator.to_h)})
|
|
622
|
+
scope_json = JSON.generate({"name" => scope.name, "version" => scope.version || ""})
|
|
623
|
+
head = %({"resourceSpans":[{"resource":#{resource},"scopeSpans":[{"scope":#{scope_json},"spans":[)
|
|
624
|
+
tail = "]}]}]}"
|
|
625
|
+
RequestEnvelope.new(head, tail, (head + tail).bytesize)
|
|
714
626
|
end
|
|
715
627
|
|
|
716
|
-
def
|
|
717
|
-
|
|
628
|
+
def encode_request(envelope, spans)
|
|
629
|
+
envelope.head + spans.map(&:encoded).join(",") + envelope.tail
|
|
718
630
|
end
|
|
719
631
|
|
|
720
632
|
def span_to_otlp(span)
|
data/lib/bitfab/transport.rb
CHANGED
|
@@ -7,8 +7,8 @@ module Bitfab
|
|
|
7
7
|
# mechanism, so callers never depend on OpenTelemetry directly.
|
|
8
8
|
module Transport
|
|
9
9
|
class << self
|
|
10
|
-
def create_trace_transport(
|
|
11
|
-
Otel.create_transport(
|
|
10
|
+
def create_trace_transport(direct_sender:)
|
|
11
|
+
Otel.create_transport(direct_sender:)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def flush_trace_transports(timeout = 30.0)
|
data/lib/bitfab/version.rb
CHANGED
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.34.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -43,20 +43,6 @@ dependencies:
|
|
|
43
43
|
- - "<"
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
45
|
version: '2'
|
|
46
|
-
- !ruby/object:Gem::Dependency
|
|
47
|
-
name: opentelemetry-exporter-otlp
|
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
|
49
|
-
requirements:
|
|
50
|
-
- - ">="
|
|
51
|
-
- !ruby/object:Gem::Version
|
|
52
|
-
version: '0.30'
|
|
53
|
-
type: :development
|
|
54
|
-
prerelease: false
|
|
55
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
-
requirements:
|
|
57
|
-
- - ">="
|
|
58
|
-
- !ruby/object:Gem::Version
|
|
59
|
-
version: '0.30'
|
|
60
46
|
- !ruby/object:Gem::Dependency
|
|
61
47
|
name: rake
|
|
62
48
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -157,7 +143,6 @@ files:
|
|
|
157
143
|
- lib/bitfab/http_client.rb
|
|
158
144
|
- lib/bitfab/mock_override.rb
|
|
159
145
|
- lib/bitfab/otel.rb
|
|
160
|
-
- lib/bitfab/otel_collector.rb
|
|
161
146
|
- lib/bitfab/replay.rb
|
|
162
147
|
- lib/bitfab/replay_branch.rb
|
|
163
148
|
- lib/bitfab/serialize.rb
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "opentelemetry-exporter-otlp"
|
|
4
|
-
|
|
5
|
-
module Bitfab
|
|
6
|
-
module Otel
|
|
7
|
-
# OTLP/HTTP protobuf delivery to a customer's Collector.
|
|
8
|
-
class CollectorExporter < OpenTelemetry::Exporter::OTLP::Exporter
|
|
9
|
-
def self.traces_endpoint(endpoint)
|
|
10
|
-
base = endpoint.chomp("/")
|
|
11
|
-
base.end_with?("/v1/traces") ? base : "#{base}/v1/traces"
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def initialize(api_key:, **options)
|
|
15
|
-
super(**options)
|
|
16
|
-
@api_key = api_key
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# The OTLP exporter reads headers once at construction, while a Bitfab key
|
|
20
|
-
# is resolved at request time (an env var may load after the client is built).
|
|
21
|
-
def export(span_data, timeout: nil)
|
|
22
|
-
key = @api_key.respond_to?(:call) ? @api_key.call : @api_key
|
|
23
|
-
@headers = @headers.merge("Authorization" => "Bearer #{key}")
|
|
24
|
-
super
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|