-
Notifications
You must be signed in to change notification settings - Fork 287
feat: add OTLP/JSON (http/json) export support #2238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
fd7c898
9fd74ab
b2dbeae
7710b77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,23 @@ def as_encoded_etsr(span_data) | |||||
| nil | ||||||
| end | ||||||
|
|
||||||
| # As JSON etsr (ExportTraceServiceRequest) | ||||||
| # | ||||||
| # Serializes to spec-compliant OTLP/JSON: trace/span ids as hex strings and | ||||||
| # enum values as integers. | ||||||
| # | ||||||
| # @param [Enumerable<OpenTelemetry::SDK::Trace::SpanData>] span_data the | ||||||
| # list of recorded {OpenTelemetry::SDK::Trace::SpanData} structs to be | ||||||
| # encoded. | ||||||
| # | ||||||
| # @return [String] returns a JSON encoded ETSR of the provided span data | ||||||
| def as_json_etsr(span_data) | ||||||
| as_etsr(span_data, json: true).to_json(format_enums_as_integers: true) | ||||||
| rescue StandardError => e | ||||||
| OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::Common#as_json_etsr') | ||||||
| nil | ||||||
| end | ||||||
|
|
||||||
| # As etsr (ExportTraceServiceRequest) | ||||||
| # | ||||||
| # @param [Enumerable<OpenTelemetry::SDK::Trace::SpanData>] span_data the | ||||||
|
|
@@ -43,7 +60,7 @@ def as_encoded_etsr(span_data) | |||||
| # | ||||||
| # @return [Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest] | ||||||
| # returns an ETSR of the provided span data | ||||||
| def as_etsr(span_data) | ||||||
| def as_etsr(span_data, json: false) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be better to avoid a mixture of named & positional arguments so something like
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like your idea of using a positional argument here. Also, what are your thoughts on making this a named argument for public methods, but keep it positional for private ones? |
||||||
| Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new( | ||||||
| resource_spans: span_data | ||||||
| .group_by(&:resource) | ||||||
|
|
@@ -60,7 +77,7 @@ def as_etsr(span_data) | |||||
| name: il.name, | ||||||
| version: il.version | ||||||
| ), | ||||||
| spans: sds.map { |sd| as_otlp_span(sd) } | ||||||
| spans: sds.map { |sd| as_otlp_span(sd, json: json) } | ||||||
| ) | ||||||
| end | ||||||
| ) | ||||||
|
|
@@ -70,36 +87,22 @@ def as_etsr(span_data) | |||||
|
|
||||||
| private | ||||||
|
|
||||||
| def as_otlp_span(span_data) | ||||||
| def as_otlp_span(span_data, json: false) | ||||||
| parent_span_id = span_data.parent_span_id == OpenTelemetry::Trace::INVALID_SPAN_ID ? nil : span_data.parent_span_id | ||||||
| Opentelemetry::Proto::Trace::V1::Span.new( | ||||||
| trace_id: span_data.trace_id, | ||||||
| span_id: span_data.span_id, | ||||||
| trace_id: format_id(span_data.trace_id, json: json), | ||||||
| span_id: format_id(span_data.span_id, json: json), | ||||||
| trace_state: span_data.tracestate.to_s, | ||||||
| parent_span_id: span_data.parent_span_id == OpenTelemetry::Trace::INVALID_SPAN_ID ? nil : span_data.parent_span_id, | ||||||
| parent_span_id: format_id(parent_span_id, json: json), | ||||||
| name: span_data.name, | ||||||
| kind: as_otlp_span_kind(span_data.kind), | ||||||
| start_time_unix_nano: span_data.start_timestamp, | ||||||
| end_time_unix_nano: span_data.end_timestamp, | ||||||
| attributes: span_data.attributes&.map { |k, v| as_otlp_key_value(k, v) }, | ||||||
| dropped_attributes_count: span_data.total_recorded_attributes - span_data.attributes&.size.to_i, | ||||||
| events: span_data.events&.map do |event| | ||||||
| Opentelemetry::Proto::Trace::V1::Span::Event.new( | ||||||
| time_unix_nano: event.timestamp, | ||||||
| name: event.name, | ||||||
| attributes: event.attributes&.map { |k, v| as_otlp_key_value(k, v) } | ||||||
| # TODO: track dropped_attributes_count in Span#append_event | ||||||
| ) | ||||||
| end, | ||||||
| events: span_data.events&.map { |event| as_otlp_span_event(event) }, | ||||||
| dropped_events_count: span_data.total_recorded_events - span_data.events&.size.to_i, | ||||||
| links: span_data.links&.map do |link| | ||||||
| Opentelemetry::Proto::Trace::V1::Span::Link.new( | ||||||
| trace_id: link.span_context.trace_id, | ||||||
| span_id: link.span_context.span_id, | ||||||
| trace_state: link.span_context.tracestate.to_s, | ||||||
| attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) } | ||||||
| # TODO: track dropped_attributes_count in Span#trim_links | ||||||
| ) | ||||||
| end, | ||||||
| links: span_data.links&.map { |link| as_otlp_span_link(link, json: json) }, | ||||||
| dropped_links_count: span_data.total_recorded_links - span_data.links&.size.to_i, | ||||||
| status: span_data.status&.then do |status| | ||||||
| Opentelemetry::Proto::Trace::V1::Status.new( | ||||||
|
|
@@ -110,6 +113,35 @@ def as_otlp_span(span_data) | |||||
| ) | ||||||
| end | ||||||
|
|
||||||
| def as_otlp_span_event(event) | ||||||
| Opentelemetry::Proto::Trace::V1::Span::Event.new( | ||||||
| time_unix_nano: event.timestamp, | ||||||
| name: event.name, | ||||||
| attributes: event.attributes&.map { |k, v| as_otlp_key_value(k, v) } | ||||||
| # TODO: track dropped_attributes_count in Span#append_event | ||||||
| ) | ||||||
| end | ||||||
|
|
||||||
| def as_otlp_span_link(link, json: false) | ||||||
| Opentelemetry::Proto::Trace::V1::Span::Link.new( | ||||||
| trace_id: format_id(link.span_context.trace_id, json: json), | ||||||
| span_id: format_id(link.span_context.span_id, json: json), | ||||||
| trace_state: link.span_context.tracestate.to_s, | ||||||
| attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) } | ||||||
| # TODO: track dropped_attributes_count in Span#trim_links | ||||||
| ) | ||||||
| end | ||||||
|
|
||||||
| # OTLP/JSON requires trace/span ids as hex, but protobuf JSON base64-encodes. | ||||||
| # For the JSON path, applying initial base64 decoding to the hex string | ||||||
| # yields bytes that protobuf re-encodes back into that hex string. | ||||||
| def format_id(id_bytes, json:) | ||||||
| return id_bytes unless json | ||||||
| return id_bytes if id_bytes.nil? || id_bytes.empty? | ||||||
|
|
||||||
| id_bytes.unpack1('H*').unpack1('m0') | ||||||
| end | ||||||
|
|
||||||
| def as_otlp_status_code(code) | ||||||
| case code | ||||||
| when OpenTelemetry::Trace::Status::OK then Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_OK | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,8 +37,10 @@ def initialize(endpoint: nil, | |
| ssl_verify_mode: fetch_ssl_verify_mode, | ||
| headers: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS', default: {}), | ||
| compression: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION', default: 'gzip'), | ||
| timeout: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10)) | ||
| timeout: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10), | ||
| protocol: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_PROTOCOL', 'OTEL_EXPORTER_OTLP_PROTOCOL', default: 'http/protobuf')) | ||
| raise ArgumentError, "unsupported compression key #{compression}" unless compression.nil? || %w[gzip none].include?(compression) | ||
| raise ArgumentError, "unsupported protocol #{protocol}" unless %w[http/json http/protobuf].include?(protocol) | ||
|
|
||
| @uri = prepare_endpoint(endpoint) | ||
|
|
||
|
|
@@ -48,6 +50,7 @@ def initialize(endpoint: nil, | |
| @headers = prepare_headers(headers) | ||
| @timeout = timeout.to_f | ||
| @compression = compression | ||
| @content_type = protocol == 'http/json' ? 'application/json' : 'application/x-protobuf' | ||
| @shutdown = false | ||
| end | ||
|
|
||
|
|
@@ -61,7 +64,12 @@ def initialize(endpoint: nil, | |
| def export(span_data, timeout: nil) | ||
| return FAILURE if @shutdown | ||
|
|
||
| send_bytes(OpenTelemetry::Exporter::OTLP::Common.as_encoded_etsr(span_data), timeout: timeout) | ||
| bytes = if @content_type == 'application/json' | ||
| OpenTelemetry::Exporter::OTLP::Common.as_json_etsr(span_data) | ||
| else | ||
| OpenTelemetry::Exporter::OTLP::Common.as_encoded_etsr(span_data) | ||
| end | ||
| send_bytes(bytes, timeout: timeout) | ||
|
Comment on lines
-64
to
+69
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update based on above |
||
| end | ||
|
|
||
| # Called when {OpenTelemetry::SDK::Trace::TracerProvider#force_flush} is called, if | ||
|
|
@@ -135,7 +143,7 @@ def send_bytes(bytes, timeout:) # rubocop:disable Metrics/MethodLength | |
| bytes | ||
| end | ||
| request.body = body | ||
| request.add_field('Content-Type', 'application/x-protobuf') | ||
| request.add_field('Content-Type', @content_type) | ||
| @headers.each { |key, value| request.add_field(key, value) } | ||
|
|
||
| remaining_timeout = OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we consolidate this into the existing method with a new protocol parameter to control the difference.