From a520ed15b88bfcf1e2d4dfaa21b7e2807eb76260 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Mon, 1 Jun 2026 16:41:37 -0600 Subject: [PATCH] gzip otel payload --- lib/braintrust/config.rb | 26 ++++++++++++--- lib/braintrust/trace.rb | 4 ++- lib/braintrust/trace/span_exporter.rb | 8 +++-- test/braintrust/config_test.rb | 36 ++++++++++++++++++++- test/braintrust/trace/span_exporter_test.rb | 19 +++++++++++ 5 files changed, 85 insertions(+), 8 deletions(-) diff --git a/lib/braintrust/config.rb b/lib/braintrust/config.rb index 336f8459..67064dd5 100644 --- a/lib/braintrust/config.rb +++ b/lib/braintrust/config.rb @@ -5,10 +5,10 @@ module Braintrust # and allows overriding with explicit options class Config attr_reader :api_key, :org_name, :default_project, :app_url, :api_url, - :filter_ai_spans, :span_filter_funcs + :filter_ai_spans, :span_filter_funcs, :compress_otel_payload def initialize(api_key: nil, org_name: nil, default_project: nil, app_url: nil, api_url: nil, - filter_ai_spans: nil, span_filter_funcs: nil) + filter_ai_spans: nil, span_filter_funcs: nil, compress_otel_payload: true) @api_key = api_key @org_name = org_name @default_project = default_project @@ -16,6 +16,7 @@ def initialize(api_key: nil, org_name: nil, default_project: nil, app_url: nil, @api_url = api_url @filter_ai_spans = filter_ai_spans @span_filter_funcs = span_filter_funcs || [] + @compress_otel_payload = compress_otel_payload end # Create a Config from environment variables, with option overrides @@ -27,9 +28,10 @@ def initialize(api_key: nil, org_name: nil, default_project: nil, app_url: nil, # @param api_url [String, nil] API URL (overrides BRAINTRUST_API_URL env var) # @param filter_ai_spans [Boolean, nil] Enable AI span filtering (overrides BRAINTRUST_OTEL_FILTER_AI_SPANS env var) # @param span_filter_funcs [Array, nil] Custom span filter functions + # @param compress_otel_payload [Boolean, nil] Gzip OTEL export payloads (overrides BRAINTRUST_COMPRESS_OTEL_PAYLOAD env var). Default: true # @return [Config] the created config def self.from_env(api_key: nil, org_name: nil, default_project: nil, app_url: nil, api_url: nil, - filter_ai_spans: nil, span_filter_funcs: nil) + filter_ai_spans: nil, span_filter_funcs: nil, compress_otel_payload: nil) # Parse filter_ai_spans from ENV if not explicitly provided env_filter_ai_spans = ENV["BRAINTRUST_OTEL_FILTER_AI_SPANS"] filter_ai_spans_value = if filter_ai_spans.nil? @@ -38,6 +40,13 @@ def self.from_env(api_key: nil, org_name: nil, default_project: nil, app_url: ni filter_ai_spans end + # Gzip OTEL payloads by default; disable via env var if not explicitly provided + compress_otel_payload_value = if compress_otel_payload.nil? + parse_bool(ENV["BRAINTRUST_COMPRESS_OTEL_PAYLOAD"], default: true) + else + compress_otel_payload + end + new( api_key: api_key || ((ENV["BRAINTRUST_API_KEY"] && ENV["BRAINTRUST_API_KEY"].empty?) ? nil : ENV["BRAINTRUST_API_KEY"]), org_name: org_name || ENV["BRAINTRUST_ORG_NAME"], @@ -45,8 +54,17 @@ def self.from_env(api_key: nil, org_name: nil, default_project: nil, app_url: ni app_url: app_url || ENV["BRAINTRUST_APP_URL"] || "https://www.braintrust.dev", api_url: api_url || ENV["BRAINTRUST_API_URL"] || "https://api.braintrust.dev", filter_ai_spans: filter_ai_spans_value, - span_filter_funcs: span_filter_funcs + span_filter_funcs: span_filter_funcs, + compress_otel_payload: compress_otel_payload_value ) end + + # Parse a boolean-ish env var value. Falsy values: "false", "0", "no", "off" + # (case-insensitive). nil/empty falls back to the default. + def self.parse_bool(value, default:) + return default if value.nil? || value.empty? + + !%w[false 0 no off].include?(value.strip.downcase) + end end end diff --git a/lib/braintrust/trace.rb b/lib/braintrust/trace.rb index 2c6563fe..d27db780 100644 --- a/lib/braintrust/trace.rb +++ b/lib/braintrust/trace.rb @@ -89,9 +89,11 @@ def self.enable(tracer_provider, state: nil, exporter: nil, config: nil) config ||= state.respond_to?(:config) ? state.config : nil # Create OTLP HTTP exporter unless override provided + compress = config.nil? || config.compress_otel_payload exporter ||= SpanExporter.new( endpoint: "#{state.api_url}/otel/v1/traces", - api_key: state.api_key + api_key: state.api_key, + compress: compress ) # Use SimpleSpanProcessor for InMemorySpanExporter (testing), BatchSpanProcessor for production diff --git a/lib/braintrust/trace/span_exporter.rb b/lib/braintrust/trace/span_exporter.rb index fc7502fa..4c2b2390 100644 --- a/lib/braintrust/trace/span_exporter.rb +++ b/lib/braintrust/trace/span_exporter.rb @@ -17,8 +17,12 @@ class SpanExporter < OpenTelemetry::Exporter::OTLP::Exporter SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE - def initialize(endpoint:, api_key:) - super(endpoint: endpoint, headers: {"Authorization" => "Bearer #{api_key}"}) + def initialize(endpoint:, api_key:, compress: true) + super( + endpoint: endpoint, + headers: {"Authorization" => "Bearer #{api_key}"}, + compression: compress ? "gzip" : "none" + ) end def export(span_data, timeout: nil) diff --git a/test/braintrust/config_test.rb b/test/braintrust/config_test.rb index 0c9edbef..597875d2 100644 --- a/test/braintrust/config_test.rb +++ b/test/braintrust/config_test.rb @@ -6,7 +6,8 @@ "BRAINTRUST_API_KEY" => ENV["BRAINTRUST_API_KEY"], "BRAINTRUST_ORG_NAME" => ENV["BRAINTRUST_ORG_NAME"], "BRAINTRUST_APP_URL" => ENV["BRAINTRUST_APP_URL"], - "BRAINTRUST_API_URL" => ENV["BRAINTRUST_API_URL"] + "BRAINTRUST_API_URL" => ENV["BRAINTRUST_API_URL"], + "BRAINTRUST_COMPRESS_OTEL_PAYLOAD" => ENV["BRAINTRUST_COMPRESS_OTEL_PAYLOAD"] }.freeze class Braintrust::ConfigTest < Minitest::Test @@ -61,4 +62,37 @@ def test_env_vars_override_defaults assert_equal "https://custom.braintrust.dev", config.app_url end + + def test_compress_otel_payload_defaults_to_true + config = Braintrust::Config.from_env + + assert_equal true, config.compress_otel_payload + end + + def test_compress_otel_payload_disabled_by_env_var + %w[false 0 no off FALSE Off].each do |val| + ENV["BRAINTRUST_COMPRESS_OTEL_PAYLOAD"] = val + + config = Braintrust::Config.from_env + + assert_equal false, config.compress_otel_payload, + "expected #{val.inspect} to disable compression" + end + end + + def test_compress_otel_payload_truthy_env_var + ENV["BRAINTRUST_COMPRESS_OTEL_PAYLOAD"] = "true" + + config = Braintrust::Config.from_env + + assert_equal true, config.compress_otel_payload + end + + def test_compress_otel_payload_explicit_overrides_env_var + ENV["BRAINTRUST_COMPRESS_OTEL_PAYLOAD"] = "false" + + config = Braintrust::Config.from_env(compress_otel_payload: true) + + assert_equal true, config.compress_otel_payload + end end diff --git a/test/braintrust/trace/span_exporter_test.rb b/test/braintrust/trace/span_exporter_test.rb index 45a5c753..1f618f16 100644 --- a/test/braintrust/trace/span_exporter_test.rb +++ b/test/braintrust/trace/span_exporter_test.rb @@ -163,6 +163,25 @@ def test_empty_span_data assert_equal 0, exporter.calls.length end + def test_uses_gzip_compression_by_default + exporter = Braintrust::Trace::SpanExporter.new( + endpoint: "https://example.com/otel/v1/traces", + api_key: "test-key" + ) + + assert_equal "gzip", exporter.instance_variable_get(:@compression) + end + + def test_compression_disabled_when_compress_false + exporter = Braintrust::Trace::SpanExporter.new( + endpoint: "https://example.com/otel/v1/traces", + api_key: "test-key", + compress: false + ) + + assert_equal "none", exporter.instance_variable_get(:@compression) + end + def test_header_cleaned_up_even_on_error exporter = RecordingExporter.new exporter.define_singleton_method(:send_bytes) do |data, timeout:|