-
Notifications
You must be signed in to change notification settings - Fork 355
Adding baggage limits for OT Baggage #12303
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: master
Are you sure you want to change the base?
Changes from all commits
1b4215c
e8c5cb5
740ce66
0048d3b
3424e8d
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 |
|---|---|---|
|
|
@@ -31,12 +31,17 @@ | |
| import java.util.Collections; | ||
| import java.util.Map; | ||
| import java.util.TreeMap; | ||
| import okio.Utf8; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * When adding new context fields to the ContextInterpreter class remember to clear them in the | ||
| * reset() method. | ||
| */ | ||
| public abstract class ContextInterpreter implements AgentPropagation.KeyClassifier { | ||
| private static final Logger LOG = LoggerFactory.getLogger(ContextInterpreter.class); | ||
|
|
||
| private TraceConfig traceConfig; | ||
|
|
||
| protected Map<String, String> headerTags; | ||
|
|
@@ -47,6 +52,9 @@ public abstract class ContextInterpreter implements AgentPropagation.KeyClassifi | |
| protected int samplingPriority; | ||
| protected TagMap.Ledger tagLedger; | ||
| protected Map<String, String> baggage; | ||
| private Map<String, Integer> baggageItemBytes; | ||
| private int baggageItems; | ||
| private int baggageBytes; | ||
|
|
||
| protected CharSequence lastParentId; | ||
| protected CharSequence origin; | ||
|
|
@@ -63,6 +71,11 @@ public abstract class ContextInterpreter implements AgentPropagation.KeyClassifi | |
| private final boolean aiGuardEnabled; | ||
| private boolean collectIpHeaders; | ||
| private final boolean requestHeaderTagsCommaAllowed; | ||
| private final int baggageMaxItems; | ||
| private final int baggageMaxBytes; | ||
|
|
||
| /** Longest value {@link #addReservedBaggageItem(String, String)} will store, in characters. */ | ||
| private static final int MAX_RESERVED_BAGGAGE_LENGTH = 64; | ||
|
|
||
| protected static final boolean LOG_EXTRACT_HEADER_NAMES = Config.get().isLogExtractHeaderNames(); | ||
| private static final DDCache<String, String> CACHE = DDCaches.newFixedSizeCache(64); | ||
|
|
@@ -78,6 +91,8 @@ protected ContextInterpreter(Config config) { | |
| this.aiGuardEnabled = config.isAiGuardEnabled(); | ||
| this.propagationTagsFactory = PropagationTags.factory(config); | ||
| this.requestHeaderTagsCommaAllowed = config.isRequestHeaderTagsCommaAllowed(); | ||
| this.baggageMaxItems = config.getTraceBaggageMaxItems(); | ||
| this.baggageMaxBytes = config.getTraceBaggageMaxBytes(); | ||
| } | ||
|
|
||
| final TagMap.Ledger tagLedger() { | ||
|
|
@@ -216,15 +231,72 @@ protected final boolean handleMappedBaggage(String key, String value) { | |
| final String lowerCaseKey = toLowerCase(key); | ||
| final String mappedKey = baggageMapping.get(lowerCaseKey); | ||
| if (null != mappedKey) { | ||
| if (baggage.isEmpty()) { | ||
| baggage = new TreeMap<>(); | ||
| } | ||
| baggage.put(mappedKey, HttpCodec.decode(value)); | ||
| addBaggageItem(mappedKey, value); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| protected final boolean addBaggageItem(String key, String value) { | ||
| if (key == null || value == null || baggageMaxItems <= 0 || baggageMaxBytes <= 0) { | ||
| return false; | ||
| } | ||
| final Integer previousItemBytes = baggageItemBytes.get(key); | ||
| final boolean newItem = previousItemBytes == null; | ||
| if (newItem && baggage.containsKey(key)) { | ||
| // A reserved propagation value already owns this key. Caller-controlled baggage must not | ||
| // make preservation of that value depend on header visitation order. | ||
| LOG.debug("Dropping baggage item {}: key is reserved for propagation", key); | ||
| return false; | ||
| } | ||
| if (newItem && baggageItems >= baggageMaxItems) { | ||
| LOG.debug("Dropping baggage item {}: item limit {} reached", key, baggageMaxItems); | ||
| return false; | ||
|
mhlidd marked this conversation as resolved.
|
||
| } | ||
| // Charge the raw key and value before URL decoding so percent-encoded input keeps its size. | ||
| final long itemBytes = Utf8.size(key) + Utf8.size(value); | ||
|
Collaborator
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. ❔ question: Can you use an approximate size here rather than depending on OkHttp lib? |
||
| final long projectedBytes = (long) baggageBytes - (newItem ? 0 : previousItemBytes) + itemBytes; | ||
| if (projectedBytes > baggageMaxBytes) { | ||
| LOG.debug("Dropping baggage item {}: byte limit {} reached", key, baggageMaxBytes); | ||
| return false; | ||
| } | ||
| final String decodedValue = HttpCodec.decode(value); | ||
| if (baggage.isEmpty()) { | ||
| baggage = new TreeMap<>(); | ||
| baggageItemBytes = new TreeMap<>(); | ||
|
Collaborator
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. ❔ question: Why do we need ordering here? (TreeMap)
Contributor
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. TreeMap was used in the old diff when OT Baggage was introduced, so I kept it here. Unsure of reasons it was used then (commit message says "use TreeMap everywhere for now"), but if unnecessary I can replace it w/ a HashMap. |
||
| } | ||
| baggage.put(key, decodedValue); | ||
| // Accepted baggage is bounded by the int-valued baggageMaxBytes configuration. | ||
| baggageItemBytes.put(key, (int) itemBytes); | ||
| if (newItem) { | ||
| baggageItems++; | ||
| } | ||
| baggageBytes = (int) projectedBytes; | ||
| return true; | ||
| } | ||
|
|
||
| // Stores a value the tracer itself round-trips, outside the caller-controlled baggage budget so | ||
| // that caller-supplied headers cannot evict it. Exempt from the configured limits, but not | ||
| // unbounded: the value is capped here rather than left to each caller to validate, so the total | ||
| // retained stays within trace.baggage.max.bytes plus a fixed amount per reserved key. | ||
|
Comment on lines
+278
to
+281
Collaborator
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. 💭 thought: Why is there method comment that is not part of the Javadoc?
Contributor
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. Good point, I'll update here. Also will open a follow-up PR to add to |
||
| protected final void addReservedBaggageItem(String key, String value) { | ||
|
Collaborator
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. 💭 thought: I don't get the whole reserved baggage… If it's only for XRay and in case of conflict, I don't see the point having it. |
||
| if (key == null || value == null || value.length() > MAX_RESERVED_BAGGAGE_LENGTH) { | ||
| return; | ||
| } | ||
| if (baggage.isEmpty()) { | ||
| baggage = new TreeMap<>(); | ||
| baggageItemBytes = new TreeMap<>(); | ||
| } | ||
| // A mapped baggage key could collide with a reserved key. Remove its charge before replacing | ||
| // it so the caller-controlled budget continues to describe the items actually retained. | ||
| final Integer previousItemBytes = baggageItemBytes.remove(key); | ||
| if (previousItemBytes != null) { | ||
| baggageItems--; | ||
| baggageBytes -= previousItemBytes; | ||
| } | ||
| baggage.put(key, value); | ||
| } | ||
|
|
||
| public ContextInterpreter reset(TraceConfig traceConfig) { | ||
| this.traceConfig = traceConfig; | ||
| traceId = DDTraceId.ZERO; | ||
|
|
@@ -234,6 +306,9 @@ public ContextInterpreter reset(TraceConfig traceConfig) { | |
| endToEndStartTime = 0; | ||
| if (tagLedger != null) tagLedger.reset(); | ||
| baggage = Collections.emptyMap(); | ||
| baggageItemBytes = Collections.emptyMap(); | ||
| baggageItems = 0; | ||
| baggageBytes = 0; | ||
| valid = true; | ||
| fullContext = true; | ||
| httpHeaders = 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.
❔ question: Do we want to allow negative max item/bytes values as possible config value?
Or should we set the min as 0 during parsing? Having negative value as errors.
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.
Setting min to 0 is cleaner. This will affect W3C Baggage as well but positively. :)