Skip to content

Commit 0c3135d

Browse files
authored
Specify uniqueness of tracestate keys as the sender's responsibility (#477)
* Specify uniqueness of tracestate keys as the sender's responsibility This partially resolves #446. This adds a new rule to "Mutating the tracestate Field" that makes it clear that a sender must not create duplicated tracestate keys. It is a non-breaking change because the section "Combined Header Value" already had called that out with "Only one entry per key is allowed". Basically this adds that rule to the section it actually belongs into. Having that as the responsibility of the sender seems to be consensus in There seems to be no consensus yet whether an implementation should clean up a tracestate value it receives by discarding duplicate keys that it does not own. Thus, that aspect is not considered in this change. * remove redundant phrase about not adding your own key twice This is already called out in the section "Mutating the traceparent Field". * call out that foreign duplicated keys MAY be deleted fixes #446 * fix test case for duplicated tracestate keys The test_tracestate_duplicated_keys was broken, it demanded to drop the whole tracestate when a duplicate key was found. This does not match what the specification mandates in that situation. Instead, implementations MAY remove the duplicates from tracestate or even leave them in as-is. fixes #369
1 parent c899924 commit 0c3135d

File tree

3 files changed

+17
-14
lines changed

3 files changed

+17
-14
lines changed

spec/20-http_request_header_format.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,19 +293,19 @@ chr = %x20 / nblk-chr
293293

294294
### Combined Header Value
295295

296-
The `tracestate` value is the concatenation of trace graph key/value pairs
296+
The `tracestate` value is the concatenation of trace graph key/value pairs.
297297

298298
Example: `vendorname1=opaqueValue1,vendorname2=opaqueValue2`
299299

300-
Only one entry per key is allowed because the entry represents that last position in the trace. Hence vendors must overwrite their entry upon reentry to their tracing system.
301-
302-
For example, if a vendor name is Congo and a trace started in their system and then went through a system named Rojo and later returned to Congo, the `tracestate` value would not be:
300+
Only one entry per key is allowed. For example, if a vendor name is Congo and a trace started in their system and then went through a system named Rojo and later returned to Congo, the `tracestate` value would not be:
303301

304302
`congo=congosFirstPosition,rojo=rojosFirstPosition,congo=congosSecondPosition`
305303

306304
Instead, the entry would be rewritten to only include the most recent position:
307305
`congo=congosSecondPosition,rojo=rojosFirstPosition`
308306

307+
See [Mutating the tracestate Field](#mutating-the-tracestate-field) for details.
308+
309309
#### tracestate Limits:
310310

311311
Vendors SHOULD propagate at least 512 characters of a combined header. This length includes commas required to separate list items and optional white space (`OWS`) characters.
@@ -355,6 +355,6 @@ Vendors receiving a `tracestate` request header MUST send it to outgoing request
355355

356356
Following are allowed mutations:
357357

358-
- **Add a new key/value pair**. The new key/value pair SHOULD be added to the beginning of the list.
358+
- **Add a new key/value pair**. The new key/value pair SHOULD be added to the beginning of the list. Adding a key/value pair MUST NOT result in the same key being present multiple times.
359359
- **Update an existing value**. The value for any given key can be updated. Modified keys SHOULD be moved to the beginning (left) of the list.
360-
- **Delete a key/value pair**. Any key/value pair MAY be deleted. Vendors SHOULD NOT delete keys that were not generated by them. The deletion of an unknown key/value pair will break correlation in other systems. This mutation enables two scenarios. The first is that proxies can block certain `tracestate` keys for privacy and security concerns. The second scenario is a truncation of long `tracestate`s.
360+
- **Delete a key/value pair**. Any key/value pair MAY be deleted. Vendors SHOULD NOT delete keys that were not generated by them. The deletion of an unknown key/value pair will break correlation in other systems. This mutation enables three scenarios. The first is that proxies can block certain `tracestate` keys for privacy and security concerns. The second scenario is a truncation of long `tracestate`s. Finally, vendors MAY also discard duplicate keys that were not generated by them.

test/test.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -549,37 +549,38 @@ def test_tracestate_multiple_headers_different_keys(self):
549549
def test_tracestate_duplicated_keys(self):
550550
'''
551551
harness sends a request with an invalid tracestate header with duplicated keys
552-
expects the tracestate to be discarded
552+
expects the tracestate to be inherited, and the duplicated keys to be either kept as-is or one of them
553+
to be discarded
553554
'''
554555
traceparent, tracestate = self.make_single_request_and_get_tracecontext([
555556
['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'],
556557
['tracestate', 'foo=1,foo=1'],
557558
])
558559
self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012')
559-
self.assertRaises(KeyError, lambda: tracestate['foo'])
560+
self.assertTrue('foo=1' in str(tracestate))
560561

561562
traceparent, tracestate = self.make_single_request_and_get_tracecontext([
562563
['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'],
563564
['tracestate', 'foo=1,foo=2'],
564565
])
565566
self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012')
566-
self.assertRaises(KeyError, lambda: tracestate['foo'])
567+
self.assertTrue('foo=1' in str(tracestate) or 'foo=2' in str(tracestate))
567568

568569
traceparent, tracestate = self.make_single_request_and_get_tracecontext([
569570
['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'],
570571
['tracestate', 'foo=1'],
571572
['tracestate', 'foo=1'],
572573
])
573574
self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012')
574-
self.assertRaises(KeyError, lambda: tracestate['foo'])
575+
self.assertTrue('foo=1' in str(tracestate))
575576

576577
traceparent, tracestate = self.make_single_request_and_get_tracecontext([
577578
['traceparent', '00-12345678901234567890123456789012-1234567890123456-00'],
578579
['tracestate', 'foo=1'],
579580
['tracestate', 'foo=2'],
580581
])
581582
self.assertEqual(traceparent.trace_id.hex(), '12345678901234567890123456789012')
582-
self.assertRaises(KeyError, lambda: tracestate['foo'])
583+
self.assertTrue('foo=1' in str(tracestate) or 'foo=2' in str(tracestate))
583584

584585
def test_tracestate_all_allowed_characters(self):
585586
'''

test/tracecontext/tracestate.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ def from_string(self, string):
5656
if not match:
5757
raise ValueError('illegal key-value format {!r}'.format(member))
5858
key, eq, value = match.groups()
59-
if key in self._traits:
60-
raise ValueError('conflict key {!r}'.format(key))
61-
self._traits[key] = value
59+
if key not in self._traits:
60+
self._traits[key] = value
61+
# If key is already in self._traits, the incoming tracestate header contained a duplicated key.
62+
# According to the spec, two behaviors are valid: Either pass on the duplicated key as-is or drop
63+
# it. We opt for dropping it.
6264
return self
6365

6466
def to_string(self):

0 commit comments

Comments
 (0)