-
Notifications
You must be signed in to change notification settings - Fork 231
Add BaggageRestrictionManager #217
Add BaggageRestrictionManager #217
Conversation
Codecov Report
@@ Coverage Diff @@
## master #217 +/- ##
============================================
- Coverage 81.53% 81.39% -0.14%
- Complexity 491 504 +13
============================================
Files 79 82 +3
Lines 1895 1935 +40
Branches 229 232 +3
============================================
+ Hits 1545 1575 +30
- Misses 253 261 +8
- Partials 97 99 +2
Continue to review full report at Codecov.
|
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.
Have you given any thought to my comment about minimizing memory allocations? For example, even if you need multi-dimensional answer from baggage manager, you can achieve that without returning a value object, by using a strategy pattern, i.e. returning an actor that "does something with key/value", which could be no-op & metrics, set & metrics, truncate & set & metrics. All those actors can be pre-created by the manager once.
} | ||
SanitizedBaggage baggage = this.tracer.getBaggageRestrictionManager().sanitizeBaggage(key, value); | ||
if (!baggage.isValid()) { | ||
this.tracer.getMetrics().baggageUpdateFailure.inc(1); |
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.
might be useful to log this to span as well
I attempted the strategy pattern approach and it's a hacky mess. If you're concerned about minimizing memory allocations, maybe I can go back to my first ever approach where I only return (isValid, maxValueLength) which are created once (a minute) and cached. |
|
||
String prevItem = span.getBaggageItem(key); | ||
this.logFields(span, key, value, prevItem, truncated, false); | ||
SpanContext context = span.context().withBaggageItem(key, value); |
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.
I have to create the new context and pass it back given the setter isn't public. Also the BaggageValidity class reeks of feature envy. I could move this file down to the same namespace as Span and make the context setter protected or I could remove the functionality in this class completely and inline it in Span.setBaggageItem()
} | ||
BaggageValidity baggageValidity = this.getTracer().getBaggageRestrictionManager().isBaggageValid(key, value); |
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.
how about
- s/BaggageValidity/BaggageRestriction/
- s/isBaggageValid(key, value)/getBaggageRestriction(key)/
- s/sanitizeBaggage/setBaggage/
- move the call to getBaggageRestriction outside of
synchronized
since it's not dependent on the span
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.
or s/BaggageValidity/BaggageSetter/
(back to "strategy" pattern)
final int maxValueLength; | ||
|
||
public SpanContext sanitizeBaggage(Span span, String key, String value) { | ||
Metrics metrics = span.getTracer().getMetrics(); |
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.
I'd initialize the metrics object by accepting a Metrics when creating an instance of the class. There is no necessity to retrieve it once per method call.
See also: Law of Demeter
fields.put("override", "true"); | ||
} | ||
return this.log(fields); | ||
if (key == null || value == null) { |
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.
This test doesn't need to be synchronized.
private String serviceName; | ||
private Clock clock = new SystemClock(); | ||
private Map<String, Object> tags = new HashMap<String, Object>(); | ||
private boolean zipkinSharedRpcSpan; | ||
private ActiveSpanSource activeSpanSource = new ThreadLocalActiveSpanSource(); | ||
private BaggageRestrictionManager baggageRestrictionManager = new DefaultBaggageRestrictionManager(); | ||
private BaggageRestrictionManager baggageRestrictionManager = | ||
new DefaultBaggageRestrictionManager(this.metrics); |
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.
This introduces a bug; the instance of metrics used by DefaultBaggageRestrictionManager
would always be the one set in L453.
@@ -499,6 +508,9 @@ public Builder withZipkinSharedRpcSpan() { | |||
|
|||
public Builder withMetrics(Metrics metrics) { | |||
this.metrics = metrics; | |||
if (this.baggageRestrictionManager instanceof DefaultBaggageRestrictionManager) { | |||
this.baggageRestrictionManager = new DefaultBaggageRestrictionManager(this.metrics); | |||
} |
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.
This is bad because withMetrics
now has multiple responsibilities.
It is also incorrect in that it misses the case where metrics are initialized withStatsReporter
A better way to handle this is to leave baggageRestrictionManager
uninitialized, and initialize it in build
with the right metrics instance.
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.
dope
return context; | ||
} | ||
|
||
void logFields(Span span, String key, String value, String prevItem, boolean truncated, boolean invalid) { |
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.
This should be private
.
final Metrics metrics; | ||
|
||
public SpanContext setBaggage(Span span, String key, String value) { | ||
if (!this.isValid()) { |
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.
It's simpler to check the boolean directly instead of using the getter
public SpanContext setBaggage(Span span, String key, String value) { | ||
if (!this.isValid()) { | ||
metrics.baggageUpdateFailure.inc(1); | ||
this.logFields(span, key, value, null, false, true); |
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.
There is no need to use this
here
|
||
package com.uber.jaeger.baggage; | ||
|
||
public abstract class BaggageRestrictionManager { |
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.
Could you add javadocs
on what this is?
final int maxValueLength; | ||
final Metrics metrics; | ||
|
||
public SpanContext setBaggage(Span span, String key, String value) { |
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.
Add javadoc, simply based on the method name, I'd conclude that it simply sets baggage.
package com.uber.jaeger.baggage; | ||
|
||
public abstract class BaggageRestrictionManager { | ||
static final int DEFAULT_MAX_VALUE_LENGTH = 2048; |
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.
This is only used in the DefaultBaggageRestrictionManager
. Do you see this being made use of elsewhere? Perhaps it can be moved to DefaultBaggageRestrictionManager
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.
it'll be used in another manager in the follow up PR
} | ||
|
||
@Override | ||
public BaggageSetter getBaggageSetter(String key) { |
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.
Do we need this?
Can't we use lombok's @Getter
annotation?
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.
getBaggageSetter is an interface function
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.
discussed this offline: my suggestion doesn't work because the getter requires a key
1L, metricsReporter.counters.get("jaeger.baggage-update.result=ok").longValue()); | ||
} | ||
|
||
private void assertBaggageLogs( |
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.
It looks like testing concerns aren't separated between SpanTest and BaggageSetter.
Ideally, we would only do the following in SpanTest
Assert that span.setBaggageItem
invokes BaggageSetter
with the right arguments.
Assert that span.getBaggeItem
can retrieve baggage that has been set
All of the detailed test cases that only involve setting baggage should be under the purview of BaggageSetterTest
. This results in a clear separation of concerns and code deduplication.
af37bfb
to
72c1b84
Compare
public class BaggageSetter { | ||
final boolean valid; | ||
final int maxValueLength; | ||
final Metrics metrics; |
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.
- to make fields visible to subclasses, they should be
protected
. Did you also need package visibility? - given that these fields are final, there should be a constructor setting them. If they are only set by subclasses, then the constructor can be protected (but then this class should be abstract).
- the concept of
valid
is not clear, please document
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.
@value(staticConstructor = "of") generates a private constructor and a public factory for this class.
return context; | ||
} | ||
|
||
private void logFields(Span span, String key, String value, String prevItem, boolean truncated, boolean invalid) { |
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.
avoid negatives - s/invalid/valid/
public SpanContext setBaggage(Span span, String key, String value) { | ||
if (!valid) { | ||
metrics.baggageUpdateFailure.inc(1); | ||
logFields(span, key, value, null, false, true); |
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.
if you move prevItem and truncated declarations to the top, this could be made a lot more readable: logFields(span, key, value, prevValue, truncated, valid);
fields.put("truncated", "true"); | ||
} | ||
if (invalid) { | ||
fields.put("invalid", "true"); |
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.
why are you logging these as strings instead of booleans?
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.
ah, nvm
* This flag represents whether the key is a valid baggage key. If valid | ||
* the baggage key:value will be written to the span. | ||
*/ | ||
private final boolean valid; |
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.
If something is not a valid baggage key then why create a BaggageSetter for it?
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.
I assume it would create a Noop setter. This way the validity does not need to be exposed outside of baggage manager into the Span
|
||
@Override | ||
public BaggageSetter getBaggageSetter(String key) { | ||
BaggageSetter setter = baggageSetters.get(key); |
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.
unsynchronized map
…e SpanSetter since this wont be able to scale with the number of random baggage people can set
2aeba8f
to
495a656
Compare
fdfa73f
to
72cbdc5
Compare
Rewrote to fit into new API. |
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.
not clear how this will support per-service restrictions
/** | ||
* Sets the baggage key:value on the {@link Span} and the corresponding | ||
* logs. Whether the baggage is set on the span depends on if the key | ||
* is valid. |
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.
s/is valid/is allowed to be set by this service/
} | ||
return this.log(fields); | ||
} | ||
this.context = getTracer().setBaggage(this, key, value); |
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.
why getTracer instead of just tracer
?
* BaggageRestrictionManager is an interface for a class that manages baggage | ||
* restrictions for baggage keys. The manager will return a {@link Restriction} | ||
* for a specific baggage key which will determine whether the baggage key is | ||
* allowed and any other applicable restrictions on the baggage value. |
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.
s/is allowed/is allowed for the current service/
public interface BaggageRestrictionManager { | ||
int DEFAULT_MAX_VALUE_LENGTH = 2048; | ||
|
||
Restriction getRestriction(String key); |
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.
shouldn't we pass the service name?
I'm not sure how the whole client will support the per service use case. Maybe we should have a plan for that before making the baggagerestrictionmanager battle ready? |
the service name will be associated with the span, not the tracer. But I see how in this version you filter on the service name at the time of querying the backend, so that's ok for now. But it will be an API change in the future, so it might make sense to just pass the service name from the tracer to baggage restriction manager, even if it ignores it for now. |
I'm not sure if that's future proof. My assumption is that in this dystopian future with no rules, people will bring up a tracer without a service name which means we can't initialize the BRM with a service. This means that we have to change
|
Subset of this: https://github.com/uber/jaeger-client-java/pull/210/files. Only adding the BaggageRestrictionManager abstract class and the DefaultBaggageRestrictionManager.