Repository navigation
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3783 +/- ##
=======================================
+ Coverage 84.2% 84.4% +0.1%
=======================================
Files 123 123
Lines 30484 30789 +305
=======================================
+ Hits 25686 25989 +303
- Misses 4798 4800 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ae15bb5 to
96b5a0e
Compare
Pull request dashboard statusWaiting on reviewers · refreshed 2026-10-23 20:38 UTC Review the latest changes. Status above doesn't look right?
|
96b5a0e to
35c8d62
Compare
| let mut collection = GrowableArray::<i32>::new(); | ||
|
|
||
| // Try to modify values in an empty list | ||
| collection.retain_mut(|value| { |
There was a problem hiding this comment.
This doesn’t check that the closure never runs. Making it panic should the trick.
| #[test] | ||
| fn test_retain_mut_modify_all_elements() { | ||
| // Add more elements and modify them | ||
| let mut collection = collection_of(10); |
There was a problem hiding this comment.
With 10 elements, this only covers the inline array. Using DEFAULT_MAX_INLINE_CAPACITY + 3 and checking the full resulting sequence would cover overflow too.
|
|
||
| /// Retains only the elements for which `f` returns `true`, and preserves their order. | ||
| /// | ||
| /// This function performs the following operations in one pass: |
There was a problem hiding this comment.
IMO the method’s behavior and why we refill the inline array seem enough; the rest mostly walks through the code.
There was a problem hiding this comment.
I rephrased the comment. Tried to stick to the original PR as much as possible.
| ); | ||
| assert!(!record.attributes_contains( | ||
| &Key::from_static_str("key1"), | ||
| &AnyValue::String("value1".into()) |
There was a problem hiding this comment.
This checks "value1", but we added "val1" above. Probably better to check that "key1" is gone altogether.
There was a problem hiding this comment.
Added a commit that checks for the absence of key1.
35c8d62 to
d3434e7
Compare
| pub fn set_attribute<K, V>(&mut self, key: K, value: V) -> Option<AnyValue> | ||
| where | ||
| K: Into<Key>, | ||
| V: Into<AnyValue>, | ||
| { | ||
| let key = key.into(); | ||
| let mut new_value = Some(value.into()); | ||
| let mut old_value = None; | ||
|
|
||
| self.attributes.retain_mut(|attr| match attr { | ||
| Some((k, v)) if *k == key => match new_value.take() { | ||
| Some(new_value) => { | ||
| old_value = Some(std::mem::replace(v, new_value)); | ||
| true | ||
| } | ||
| None => false, | ||
| }, | ||
| _ => true, | ||
| }); | ||
|
|
||
| if let Some(new_value) = new_value { | ||
| self.attributes.push(Some((key, new_value))); | ||
| } | ||
| old_value | ||
| } |
There was a problem hiding this comment.
We should consider avoiding unconditional allocations of the key (i.e. if one is performing an update)
| pub fn set_attribute<K, V>(&mut self, key: K, value: V) -> Option<AnyValue> | |
| where | |
| K: Into<Key>, | |
| V: Into<AnyValue>, | |
| { | |
| let key = key.into(); | |
| let mut new_value = Some(value.into()); | |
| let mut old_value = None; | |
| self.attributes.retain_mut(|attr| match attr { | |
| Some((k, v)) if *k == key => match new_value.take() { | |
| Some(new_value) => { | |
| old_value = Some(std::mem::replace(v, new_value)); | |
| true | |
| } | |
| None => false, | |
| }, | |
| _ => true, | |
| }); | |
| if let Some(new_value) = new_value { | |
| self.attributes.push(Some((key, new_value))); | |
| } | |
| old_value | |
| } | |
| pub fn set_attribute<K, V>(&mut self, key: K, value: V) -> Option<AnyValue> | |
| where | |
| K: AsRef<str> + Into<Key>, | |
| V: Into<AnyValue>, | |
| { | |
| let mut new_value = Some(value.into()); | |
| let mut old_value = None; | |
| self.attributes.retain_mut(|attr| match attr { | |
| Some((k, v)) if k.as_str() == key.as_ref() => { | |
| match new_value.take() { | |
| Some(new_value) => { | |
| old_value = Some(std::mem::replace(v, new_value)); | |
| true | |
| } | |
| None => false, | |
| } | |
| } | |
| _ => true, | |
| }); | |
| if let Some(new_value) = new_value { | |
| self.attributes.push(Some((key.into(), new_value))); | |
| } | |
| old_value | |
| } |
| /// occurrences of the key are removed. After the call, the key occurs exactly once. | ||
| /// - If the key is not found, the attribute is added. | ||
| /// | ||
| /// A `LogProcessor` can use this method to redact a value: no copy of the old value |
There was a problem hiding this comment.
A
LogProcessorcan use this method to redact a value: no copy of the old value stays on the record.
Given that the primary use-case for these changes are centered around redaction, I wonder if it makes more sense to just introduce a function like this
/// Retains only the attributes for which `f` returns `true`.
/// Returns the number of removed attributes.
pub fn retain_attributes<F>(&mut self, mut f: F) -> usize
where
F: FnMut(&Key, &AnyValue) -> bool,This will make it easier for more general filtering logic (e.g. using pattern matching).
Likewise for updating:
pub fn update_attributes<F>(&mut self, mut f: F) -> usize
where
F: FnMut(&Key, &AnyValue) -> Option<AnyValue>There was a problem hiding this comment.
Looking at the stdlib, many map types have a mutating retain, an overwriting insert, and a remove.
I think in that case we should just keep the shorthands as is and add
pub fn retain_attributes<F>(&mut self, mut f: F) -> usize
where
F: FnMut(&Key, &mut AnyValue) -> bool,The shorthands make sure to keep allocations to a minimum now. With just the two closure-based methods, it would be up to the caller to keep allocations to a minimum.
There was a problem hiding this comment.
Do we think adding update_attributes would be valuable?
Update: Disregard, I see you've opted for a retain-mut style retain attributes method.
| /// | ||
| /// - The number of removed occurrences of the key. | ||
| /// | ||
| pub fn remove_attribute(&mut self, key: &Key) -> usize { |
There was a problem hiding this comment.
Could we accept impl AsRef<str> here? Removal only needs the key name. This would let callers pass a borrowed string without creating an owned Key, and existing &Key arguments would still work.
|
Small nit: could you update the |
8ea2624 to
a24d71b
Compare
Squash of open-telemetry#2103, rebased onto main.
d4d80c4 to
e8df720
Compare
|
I also align some comments better with the style of the std lib docs. |
- Rename `update_attribute` to `set_attribute`. The name now matches the upsert behavior, as in `Span::set_attribute`. - `set_attribute` removes other occurrences of the key, so a redaction processor removes all copies of a value. - Replace `remove_at` and `iter_mut` with one `retain_mut` pass. It moves the elements and does not clone them.
e8df720 to
0057127
Compare
|
Ah, sorry. Had to push again. A normal cargo clippy run did not complain about the now unnecessary borrow in the tests. |
This comment has been minimized.
This comment has been minimized.
|
/dashboard route:reviewers |
Done |
|
@valkum, this pull request was routed to reviewers. The handoff remains active across pushes until newer actionable human feedback arrives. Top-level feedback through this request will not return; unresolved review threads remain open. |
Fixes #1986
Changes
This PR continues #2103 by @lalitb. That PR was closed because it had no activity. The first commit is #2103, squashed and rebased onto
main, with @lalitb as the author. The second commit fixes the review comments from #2103. The later comments clean up the tests a bit to keep the diffs easier to follow.New public methods on
SdkLogRecord:set_attributegives the first occurrence of the key the new value and returns the old value. It removes all other occurrences of the key. If the key is not present, it adds the attribute and returnsNone.remove_attributeremoves all occurrences of the key and returns the number of removed attributes.A
LogProcessorcan use these methods to change or redact attributes. After a call, no copy of the old value stays on the record.Changes for the review comments on #2103:
update_attributeis nowset_attribute, because the method also adds the attribute when the key is not present.Span::set_attributeuses the same name for the same behavior.set_attributeremoves duplicate keys, so a redaction processor does not keep a copy of the old value.set_attributetakesK: Into<Key>andV: Into<AnyValue>, asadd_attributedoes. The value is moved and not cloned.GrowableArray::remove_atandGrowableArray::iter_mutare replaced by oneGrowableArray::retain_mut. It moves the elements and does not clone them. It refills the inline array from the overflow vector one time for each call, not one time for each removed element.getandpushexpect a full inline array while the overflow vector has elements, so the refill is necessary.In the previous PR it was asked why the
LogRecordtrait would not also gain these methods. Here is my take on it:LogProcessorgets&mut SdkLogRecord. In the specification, changes to a record after emit are part of the SDKReadWriteLogRecord, not of the Bridge API or Log Appender.LogRecordtrait.Merge requirement checklist
CHANGELOG.mdfiles updated for non-trivial, user-facing changesDisclosure: I had Claude Code revive the closed PR and then cleaned up the PR further myself.