Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions exporter/otlp-grpc/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ inherit_from: ../../contrib/rubocop.yml
Naming/FileName:
Exclude:
- "lib/opentelemetry-exporter-otlp-grpc.rb"
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
Metrics/BlockLength:
Enabled: false
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ module Exporter
module OTLP
module GRPC
# An OpenTelemetry trace exporter that sends spans over GRPC.
class TraceExporter
class TraceExporter # rubocop:disable Metrics/ClassLength
SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
private_constant(:SUCCESS, :FAILURE)

# Default retry count for transient errors.
RETRY_COUNT = 5
private_constant(:RETRY_COUNT)

def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'http://localhost:4317/v1/traces'),
timeout: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10),
certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'),
Expand All @@ -32,15 +36,18 @@ def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPOR

uri = URI(endpoint)

creds = if !client_key_file.nil? && !client_certificate_file.nil?
root_cert = File.read(certificate_file) unless certificate_file.nil?
client_cert = File.read(client_certificate_file) unless client_certificate_file.nil?
client_key = File.read(client_key_file) unless client_key_file.nil?

creds = if !client_key.nil? && !client_cert.nil?
# Permits constructing with nil root cert.
::GRPC::Core::ChannelCredentials.new(certificate_file, client_key_file, client_certificate_file)
elsif !certificate_file.nil?
::GRPC::Core::ChannelCredentials.new(certificate_file)
::GRPC::Core::ChannelCredentials.new(root_cert, client_key, client_cert)
elsif !root_cert.nil?
::GRPC::Core::ChannelCredentials.new(root_cert)
else
:this_channel_is_insecure
end

@client = Opentelemetry::Proto::Collector::Trace::V1::TraceService::Stub.new(
"#{uri.host}:#{uri.port}",
creds
Expand All @@ -61,8 +68,7 @@ def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPOR
def export(span_data, timeout: nil)
return FAILURE if @shutdown

@client.export(OpenTelemetry::Exporter::OTLP::Common.as_etsr(span_data))
SUCCESS
send_spans(span_data, timeout: timeout)
end

# Called when {OpenTelemetry::SDK::Trace::TracerProvider#force_flush} is called, if
Expand All @@ -83,6 +89,81 @@ def shutdown(timeout: nil)
@shutdown = true
SUCCESS
end

private

def send_spans(span_data, timeout:) # rubocop:disable Metrics/MethodLength
retry_count = 0
timeout ||= @timeout
start_time = OpenTelemetry::Common::Utilities.timeout_timestamp

loop do
remaining_timeout = OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time)
return FAILURE if remaining_timeout.zero?

request = OpenTelemetry::Exporter::OTLP::Common.as_etsr(span_data)
Comment thread
arjun-rajappa marked this conversation as resolved.
Outdated
@client.export(request, deadline: Time.now + remaining_timeout)
return SUCCESS
rescue ::GRPC::DeadlineExceeded
retry if backoff?(retry_count: retry_count += 1, reason: 'deadline_exceeded')
return FAILURE
rescue ::GRPC::Unavailable
retry if backoff?(retry_count: retry_count += 1, reason: 'unavailable')
return FAILURE
rescue ::GRPC::Cancelled
retry if backoff?(retry_count: retry_count += 1, reason: 'cancelled')
return FAILURE
rescue ::GRPC::ResourceExhausted
retry if backoff?(retry_count: retry_count += 1, reason: 'resource_exhausted')
return FAILURE
rescue ::GRPC::Aborted
retry if backoff?(retry_count: retry_count += 1, reason: 'aborted')
return FAILURE
rescue ::GRPC::Internal
retry if backoff?(retry_count: retry_count += 1, reason: 'internal')
return FAILURE
rescue ::GRPC::DataLoss
retry if backoff?(retry_count: retry_count += 1, reason: 'data_loss')
return FAILURE
rescue ::GRPC::Unauthenticated => e
OpenTelemetry.handle_error(exception: e, message: 'authentication error in OTLP::GRPC::TraceExporter#send_spans')
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: 'unauthenticated' })
return FAILURE
rescue ::GRPC::PermissionDenied => e
OpenTelemetry.handle_error(exception: e, message: 'permission denied in OTLP::GRPC::TraceExporter#send_spans')
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: 'permission_denied' })
return FAILURE
rescue ::GRPC::InvalidArgument => e
OpenTelemetry.handle_error(exception: e, message: 'invalid argument in OTLP::GRPC::TraceExporter#send_spans')
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: 'invalid_argument' })
return FAILURE
rescue ::GRPC::NotFound => e
OpenTelemetry.handle_error(exception: e, message: 'not found in OTLP::GRPC::TraceExporter#send_spans')
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: 'not_found' })
return FAILURE
rescue ::GRPC::Unimplemented => e
OpenTelemetry.handle_error(exception: e, message: 'unimplemented in OTLP::GRPC::TraceExporter#send_spans')
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: 'unimplemented' })
return FAILURE
rescue ::GRPC::BadStatus => e
OpenTelemetry.handle_error(exception: e, message: "gRPC error in OTLP::GRPC::TraceExporter#send_spans: #{e.code} - #{e.details}")
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: e.code.to_s })
return FAILURE
rescue StandardError => e
OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::GRPC::TraceExporter#send_spans')
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: e.class.to_s })
return FAILURE
end
end

def backoff?(retry_count:, reason:)
@metrics_reporter.add_to_counter('otel.otlp_exporter.failure', labels: { reason: reason })
return false if retry_count > RETRY_COUNT

sleep_interval = rand(2**retry_count)
sleep(sleep_interval)
true
end
end
end
end
Expand Down
Loading
Loading