11# frozen_string_literal: true
22
3+ require_relative '../../core/chunker'
4+ require_relative '../../core/encoding'
5+ require_relative '../../core/tag_builder'
36require_relative '../../core/transport/parcel'
7+ require_relative '../../core/transport/request'
8+ require_relative '../error'
49require_relative 'http/input'
510
611module Datadog
@@ -24,6 +29,25 @@ def initialize(parcel, serialized_tags)
2429 class Transport
2530 attr_reader :client , :apis , :default_api , :current_api_id , :logger
2631
32+ # The limit on an individual snapshot payload, aka "log line",
33+ # is 1 MB.
34+ #
35+ # TODO There is an RFC for snapshot pruning that should be
36+ # implemented to reduce the size of snapshots to be below this
37+ # limit, so that we can send a portion of the captured data
38+ # rather than dropping the snapshot entirely.
39+ MAX_SERIALIZED_SNAPSHOT_SIZE = 1024 * 1024
40+
41+ # The maximum chunk (batch) size that intake permits is 5 MB.
42+ #
43+ # Two bytes are for the [ and ] of JSON array syntax.
44+ MAX_CHUNK_SIZE = 5 * 1024 * 1024 - 2
45+
46+ # Try to send smaller payloads to avoid large network requests.
47+ # If a payload is larger than default chunk size but is under the
48+ # max chunk size, it will still get sent out.
49+ DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024
50+
2751 def initialize ( apis , default_api , logger :)
2852 @apis = apis
2953 @logger = logger
@@ -36,9 +60,45 @@ def current_api
3660 end
3761
3862 def send_input ( payload , tags )
39- json = JSON . dump ( payload )
40- parcel = EncodedParcel . new ( json )
63+ # Tags are the same for all chunks, serialize them one time.
4164 serialized_tags = Core ::TagBuilder . serialize_tags ( tags )
65+
66+ encoder = Core ::Encoding ::JSONEncoder
67+ encoded_snapshots = Core ::Utils ::Array . filter_map ( payload ) do |snapshot |
68+ encoded = encoder . encode ( snapshot )
69+ if encoded . length > MAX_SERIALIZED_SNAPSHOT_SIZE
70+ # Drop the snapshot.
71+ # TODO report via telemetry metric?
72+ logger . debug { "di: dropping too big snapshot" }
73+ nil
74+ else
75+ encoded
76+ end
77+ end
78+
79+ Datadog ::Core ::Chunker . chunk_by_size (
80+ encoded_snapshots , DEFAULT_CHUNK_SIZE ,
81+ ) . each do |chunk |
82+ # We drop snapshots that are too big earlier.
83+ # The limit on chunked payload length here is greater
84+ # than the limit on snapshot size, therefore no chunks
85+ # can exceed limits here.
86+ chunked_payload = encoder . join ( chunk )
87+
88+ # We need to rescue exceptions for each chunk so that
89+ # subsequent chunks are attempted to be sent.
90+ begin
91+ send_input_chunk ( chunked_payload , serialized_tags )
92+ rescue => exc
93+ logger . debug { "di: failed to send snapshot chunk: #{ exc . class } : #{ exc } (at #{ exc . backtrace . first } )" }
94+ end
95+ end
96+
97+ payload
98+ end
99+
100+ def send_input_chunk ( chunked_payload , serialized_tags )
101+ parcel = EncodedParcel . new ( chunked_payload )
42102 request = Request . new ( parcel , serialized_tags )
43103
44104 response = @client . send_input_payload ( request )
0 commit comments