Skip to content
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

fix!: refactor propagators to add #fields #638

Merged
merged 17 commits into from
Mar 31, 2021
Merged

Conversation

fbogsany
Copy link
Contributor

@fbogsany fbogsany commented Mar 5, 2021

Fixes #613

This is a large refactor of propagators. Part of the goal is to add the #fields method, however this also uncovered quite a few bugs, discrepancies, incorrect documentation, and deviation from the spec.

Important changes:

  • Added documentation for propagators and improved consistency & accuracy of existing docs
  • Replaced TextMapInjectors and TextMapExtractors with unified TextMapPropagators
  • Clarified that the carrier passed to #inject is expected to be mutable - inject now returns nil instead of the modified carrier - the spec states that "Carriers used at Inject are expected to be mutable." (although there is some later inconsistency)
  • Implemented #fields for each TextMapPropagator
  • Clarified references to TextMapPropagator rather than Propagator - the latter is an abstract concept in the spec, with only the former currently defined - the spec makes it clear that "A binary Propagator type will be added in the future"
  • Provided 3 implementations of TextMapPropagator in the API: a wrapper for a TextMapInjector and a TextMapExtractor pair, a CompositeTextMapPropagator that composes either a list of propagators or a list of TextMapInjectors and a list of TextMapExtractor`s, and a default no-op text map propagator

TODO

  • cleanup the tests
  • consider whether and how to enforce the spec's requirement that "In order to increase compatibility, the key/value pairs MUST only consist of US-ASCII characters that make up valid HTTP header fields as per RFC 7230."
  • update Jaeger propagator

@fbogsany
Copy link
Contributor Author

I think this is ready for review. The outstanding CI failures are in the Jaeger propagator, which I (or @fhwang) still need to update.

@fbogsany fbogsany marked this pull request as ready for review March 17, 2021 19:51
@fbogsany
Copy link
Contributor Author

I'm not sure how to square the B3 propagator requirements with the configuration options for OTEL_PROPAGATORS. The former requires the B3 propagator to

attempt to extract B3 encoded using single and multi-header formats. The single-header variant takes precedence over the multi-header version.

and

default to injecting B3 using the single-header format

while the latter only allows configuring EITHER b3 (single) OR b3multi OR both, for both injection and extraction.

I can add a B3#text_map_propagator that matches the spec requirements, but we won't have a way to set that with the environment variable. This feels like an inconsistency in the spec.

@mwear
Copy link
Member

mwear commented Mar 19, 2021

I'm not sure how to square the B3 propagator requirements with the configuration options for OTEL_PROPAGATORS.

It seems like there should be three formats for b3 in the environment variable specification. I opened open-telemetry/opentelemetry-specification#1562 to discuss options.

@mwear
Copy link
Member

mwear commented Mar 23, 2021

We discussed this at the maintainers meeting today and I opened a PR based on the discusson: open-telemetry/opentelemetry-specification#1570. tl; dr:

Option Extract Order Inject Format Env var
B3 Single Single, Multi Single b3
B3 Multi Multi, Single Multi b3multi

@fbogsany
Copy link
Contributor Author

We discussed this at the maintainers meeting today and I opened a PR based on the discusson: open-telemetry/opentelemetry-specification#1570. tl; dr:

Option Extract Order Inject Format Env var
B3 Single Single, Multi Single b3
B3 Multi Multi, Single Multi b3multi

Looks like the PR was updated to always prefer Single on extract. I think we can implement that pretty easily.

Copy link
Contributor

@arielvalentin arielvalentin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't have a lot to add, just some questions around symmetry and "food for thought"

# @param [optional Setter] setter If the optional setter is provided, it
# will be used to write context into the carrier, otherwise the default
# text map setter will be used.
def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you feel about using keyword args for all of the parameters to inject/extract?

I know for some it is idiomatic to have treated some arguments as options hash but I personally prefer keeping method arguments symmetric.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we've opted to use keyword args for optional args or where a method has "many" args. In this case, the most common usage is inject(carrier). inject(carrier: carrier) doesn't increase clarity, but increases verbosity.

def inject(carrier, context: Context.current, setter: Context::Propagation.text_map_setter)
baggage = context[ContextKeys.baggage_key]

return if baggage.nil? || baggage.empty?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return if baggage.nil? || baggage.empty?
return if baggage&.empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The results of those expressions differ:

irb(main):001:0> b = ''
=> ""
irb(main):002:0> b.nil? || b.empty?
=> true
irb(main):003:0> b&.empty?
=> true
irb(main):004:0> b = nil
=> nil
irb(main):005:0> b.nil? || b.empty?
=> true
irb(main):006:0> b&.empty?
=> nil

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!!baggage&.empty? does look sad doesn't it. Oh falsey Nils!

end

context.set_value(ContextKeys.baggage_key, baggage)
rescue StandardError
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we want to log errors in inject/extract to aid in debugging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good question. The concern is that it could become very noisy. We could log at a debug level. I think we're also using exceptions for control flow in some propagators (traceparent perhaps?), which is not ideal. Here we're just being defensive, so logging is probably the right thing to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

# @param [Array<#inject, #extract, #fields>] propagators An array of
# text map propagators
def compose_propagators(propagators)
raise ArgumentError, 'propagators must be a non-nil array' unless propagators.is_a?(Array)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we treat Empty arrays the same as nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not technically wrong to pass an empty array here, and there are ways this could happen that result in configuration warning messages already. It might be useful instead to recognize the empty case and return a NoopTextMapPropagator.

injectors = @injectors || @propagators
injectors.each do |injector|
injector.inject(carrier, context: context, setter: setter)
rescue => e # rubocop:disable Style/RescueStandardError
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask that we qualify the exception type (here and in other places)?

Suggested change
rescue => e # rubocop:disable Style/RescueStandardError
rescue StandardError => e

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do some digging to see why we do this. I feel like we had a good reason, but I don't recall what it was.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no good reason that I can see - fixing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm only fixing the propagator-related ones in this PR. The others can be addressed in a separate PR.

injectors.each do |injector|
injector.inject(carrier, context: context, setter: setter)
rescue => e # rubocop:disable Style/RescueStandardError
OpenTelemetry.logger.warn "Error in CompositePropagator#inject #{e.message}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that Logger#warn will return true. This should return nil correct?

irb(main):003:0> l = Logger.new("/dev/null")
=> #<Logger:0x00007fb55c02f738 @level=0, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007fb55c02f6e8 @datetime_format=nil>, @formatter=nil, @logdev=#<Logger::LogDevice:0x00007fb55c02f300 @shift_period_suffix="%Y%m%d", @shift_size=1048576, @shift_age=0, @filename="/dev/null", @dev=#<File:/dev/null>, @mon_mutex=#<Thread::Mutex:0x00007fb55c02f0f8>, @mon_mutex_owner_object_id=70208454752640, @mon_owner=nil, @mon_count=0>>
irb(main):004:0> l.warn "afds"
=> true

after { instrumentation.instance_variable_set(:@installed, false) }
after do
# Force re-install of instrumentation
instrumentation.instance_variable_set(:@installed, false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is not introduced in the PR but what is force re-reinstall doing? Should there be a reset method similar to that of exporter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset is only defined for the InMemorySpanExporter - it isn't part of the Exporter API - arguably that exporter should be moved out of the SDK to the common package (or somewhere else).

The force-reinstall is used in instrumentation tests (only) to avoid state leakage between tests. Re-installing instrumentation is not a supported operation in general.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

@fbogsany
Copy link
Contributor Author

Status: I've merged the baggage changes & resolved conflicts. The outstanding piece is updating the xray propagator. I'll try to complete that tonight or tomorrow. It is isolated, though, and won't invalidate reviews of the rest of this PR.

Copy link
Member

@mwear mwear left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the huge refactor @fbogsany!

Copy link
Contributor

@robertlaurin robertlaurin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇

@fbogsany fbogsany merged commit 6bca875 into main Mar 31, 2021
@fbogsany fbogsany deleted the propagator-refactor branch March 31, 2021 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement Fields in Propagator
5 participants