Skip to content

feat: introduce the observed_utils crate - #677

Open
Evgenii (Vaiz) wants to merge 11 commits into
mainfrom
feat/observed-helpers
Open

feat: introduce the observed_utils crate#677
Evgenii (Vaiz) wants to merge 11 commits into
mainfrom
feat/observed-helpers

Conversation

@Vaiz

@Vaiz Evgenii (Vaiz) commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Move the OpenTelemetry helper crate from the internal repo into this repository, following observed, observed_macros and observed_testing. It is the optional companion for consumers that export typed events to OpenTelemetry.

The crate is named observed_utils and starts at version 0.1.0. It ships internally as observed_helpers at 0.24.0; both were changed on review here, while nothing is published and the change is therefore free.

observed itself no longer depends on opentelemetry, so an exporter has to convert values on its own. That conversion lives here once rather than being re-derived in every exporter: any_value_of, otel_value_of and otel_severity_of. The crate also carries format_any_value, which renders an AnyValue in human-readable form instead of its Debug shape, and SensitiveSlice, a bounded, type-erased collection of RedactedDisplay references that renders without allocating.

Changes made during the move:

  • any_value_of and otel_value_of handle Value::U64. The variant was added in 0.24 after this crate was written, so a u64 dimension used to fall through to the #[non_exhaustive] guard arm and be exported as a debug string. Values past i64::MAX still export as a decimal string, because neither AnyValue nor opentelemetry::Value has an unsigned variant.
  • metric_number_of returns to this crate. It was parked in observed_testing because that was its only consumer here; observed_testing now uses it from this crate instead of keeping a second copy.
  • The example uses the #[path = "support/taxonomy.rs"] stand-in taxonomy the observed examples use, instead of the ox-sdk-only microsoft_enterprise_data_taxonomy crate, and expect with a message instead of unwrap, to satisfy this workspace's clippy::unwrap_used policy.
  • The #![doc(html_logo_url)] and #![cfg_attr(coverage_nightly, ...)] attributes are enabled, matching the convention of the other crates here.
  • arrayvec and const-hex are added to [workspace.dependencies].

Effects:

  • A crate that emits typed events and exports them no longer has to write its own Value mapping, and the mapping is governed by this repository's gates.
  • observed_testing gains a dependency on observed_utils; the two copies of metric_number_of can no longer drift apart.
  • Telemetry helper changes no longer need a manual transfer between two repositories.
  • The crate name and version now differ from the internal copy until the follow-up PR removes it there.

Review changes

Rename and version (review request)

observed_helpers becomes observed_utils, and the version becomes 0.1.0 instead of inheriting observed's 0.24.0. Renamed in place: the directory, the package name, the repository and doc-URL attributes, both observed_testing call sites and its dependency entry, the [workspace.dependencies] entry, two prose references under crates/observed, and the lockfile. Git records every file as a rename, so the move carries no content change. The README is regenerated by cargo-doc2readme rather than hand-edited.

Correctness: an unreachable overflow probe removed

SensitiveSlice::new ended with a post-loop iter.next() used to detect overflow. It cannot be reached: a for loop exits normally only after next() returned None, so the extra call could report Some only for an iterator that resumes after None, which the Iterator contract leaves unspecified. The loop already detects overflow on its own, because is_full() is checked before the push, so the N+1-th item is pulled, observed and discarded.

The gates could not have caught this: the line executed on every non-overflowing call, so it counted as covered, and is_some() was simply always false.

Bytes render without allocating (review request)

format_any_value's Bytes arm used const_hex::encode, which returns an owned String. It now uses const_hex::display, a Display adapter that writes through const-hex's own encoder straight into the formatter, so every arm writes directly and rendering allocates nothing.

display is not gated behind const-hex's alloc feature and encode was the only user of it, so the feature is dropped. const-hex enables proptest through alloc, and nothing else in the workspace pulled it in, so proptest, rand_xorshift and unarray leave Cargo.lock too.

Other review fixes

  • Spellcheck: implementors becomes implementers and the British signalled becomes signaled. Two genuinely new technical words introduced by the module docs, recursing and redactable, were added to .spelling.
  • External types: the approved-type list names opentelemetry::logs::record::Severity, the canonical path cargo-check-external-types reports, instead of the re-export path.
  • Docs: the example's module doc said SensitiveCollection, a type that does not exist; examples/support/taxonomy.rs claimed to be shared by the observed crate's examples, and now describes this crate's copy and why the copy exists; format_any_value.rs and sensitive_slice.rs gained //! headers, matching otel.rs.
  • Tests: added for the paths nothing reached - AnyValue::Map rendering, metric_number_of on Value::U64, otel_value_of on Bool / F64 / BoolArray / F64Array, Text::Shared, and the Debug and Clone impls of SensitiveSlice.

Deliberate decision: the #[non_exhaustive] guard arms stay

The only lines no test reaches are the wildcard arms guarding the foreign #[non_exhaustive] enums observed::Value, observed::Text and opentelemetry::logs::AnyValue. All variants that exist today are matched explicitly, so those arms are unreachable and cannot be covered by a test. They are kept on purpose: they are what makes a variant added upstream degrade to a readable debug form instead of failing to compile or silently dropping data.

Rather than delete them for the 100 % line-coverage gate, the four dispatch functions carry #[cfg_attr(coverage_nightly, coverage(off))] with a comment stating exactly what the exclusion covers - including that the inline scalar and Bytes arms are excluded too, and are pinned by tests and mutation testing rather than by the gate. The work they delegate to (unsigned_any_value, unsigned_otel_value, list_of, write_list / write_map) stays measured.

cargo-deny: h2 advisory fixed, stale ignores removed

anvil-deny failed on the advisory "h2 unbounded empty DATA frames" against h2 0.4.15, which this repository carries transitively. The advisory predates this branch, but the fix is a one-line lockfile bump, so it is included: h2 moves to the patched 0.4.16. Only the h2 entry is touched - a full cargo update h2 also re-resolved unrelated windows-sys edges downwards.

Two ignores that no longer match any crate were removed from deny.toml: RUSTSEC-2025-0141 (bincode, no longer in the lockfile at all) and RUSTSEC-2026-0173 (proc-macro-error2, still present but no longer matched). The quick-xml ignores stay, because quick-xml 0.39.4 is still pulled in through the Azure SDK. cargo deny check all now reports advisories, bans, licenses and sources ok with no advisory-not-detected warnings.

Verification

On the renamed crate: tests, clippy -D warnings, doc, both rustfmt configs, doc2readme --check, spellcheck, cargo-sort, cargo-machete and cargo deny check all are clean; coverage is 100 % with no uncovered lines; cargo mutants over the package reports no surviving mutants.

Second review round: public documentation corrected

Three claims in the public documentation were not supported by the implementation as written. All three are fixed as documentation; no behaviour changed.

  • The u64 threshold is now public. any_value_of and otel_value_of described their result as the value's OpenTelemetry counterpart, but a Value::U64 has no single output type: up to i64::MAX it becomes the integer variant, past it a decimal string. That boundary was documented only on the private helpers, which never reach rustdoc. It is now on both public functions and in the crate overview. The policy itself is unchanged - neither destination enum has an unsigned variant, so the alternatives are wrapping into a negative number or dropping the value.
  • SensitiveSlice's capacity has a lower bound. N was documented only as the maximum number of stored items, while new asserts N > 0 at compile time. The type parameter and the constructor now state it.
  • SensitiveSlice's guarantees are scoped to what the wrapper controls. The module said items are "never rendered in the clear by this module" and the crate said rendering happens "without allocating". Rendering is delegated to each item's own RedactedDisplay implementation under the caller's Redactor, which is permitted to pass values through, and an item may allocate. The wording now promises inline storage of the collection and names the delegation instead.

Declined in this PR: changing metric_number_of to preserve the numeric kind, or returning it to observed_testing. The precision analysis is correct and is already acknowledged by the cast_precision_loss expectations at both call sites, but a new numeric contract is a redesign of a helper this PR only moves, and returning it to observed_testing reverses the de-duplication that is part of the move. Follow-up material, together with the adjacent-u64 boundary test suggested in review.

Third review round: the Sync bound is documented

SensitiveSlice was documented as storing &dyn RedactedDisplay fat pointers, and new as accepting implementers of RedactedDisplay. The stored type is actually &(dyn RedactedDisplay + Sync) and the constructor requires T: RedactedDisplay + Sync, so a caller with a non-Sync item type satisfied the documented bound and still failed to compile.

The bound is documented rather than removed. &(dyn Trait + Sync) is itself Send and Sync while a bare &dyn Trait is neither, so the bound is what lets an event struct holding a SensitiveSlice cross threads; dropping it would narrow where the type can be used. Documentation only, and the generated README is unaffected because these docs live on the type rather than in lib.rs.


Rebase note (2026-08-19): rebased onto main after it advanced; the branch had gone into a conflicting state. The only conflict was in Cargo.lock, where upstream's new arty_executor package entry landed in the same alphabetical slot as this branch's arrayvec entry. Both were kept, in alphabetical order; cargo metadata --locked accepts the merged lockfile. No source change.

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

✅ Version increments look sufficient

cargo semver-checks compared the 1 crate(s) this PR publishes against their previous version-bump commit in git history. Every version increment is sufficient for the detected API changes.

Crate Baseline Baseline commit This PR Minimum required Status
observed_utils new crate 0.1.0 0.1.0 ✅ ok

This check is informational and does not block the merge.

View the check run

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (a6ca774) to head (3763be2).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #677   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         554      557    +3     
  Lines       61509    61583   +74     
=======================================
+ Hits        61509    61583   +74     
Flag Coverage Δ
linux 69.2% <100.0%> (?)
linux-arm 68.5% <100.0%> (?)
windows 69.2% <100.0%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread crates/observed_helpers/examples/sensitive_collection.rs Outdated
Comment thread crates/observed_helpers/examples/support/taxonomy.rs Outdated
Comment thread crates/observed_utils/src/format_any_value.rs
Comment thread crates/observed_utils/src/format_any_value.rs
Comment thread crates/observed_helpers/src/sensitive_slice.rs Outdated
Comment thread crates/observed_helpers/Cargo.toml Outdated
@Vaiz Evgenii (Vaiz) changed the title feat: introduce the observed_helpers crate feat: introduce the observed_utils crate Aug 18, 2026
@Vaiz
Evgenii (Vaiz) requested a lite review from Copilot August 18, 2026 14:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new observed_utils crate that centralizes OpenTelemetry conversion helpers and related utilities for observed ecosystem consumers, and wires it into the workspace (including observed_testing) while cleaning up cargo-deny advisory configuration.

Changes:

  • Added the new crates/observed_utils crate (OTel conversions, SensitiveSlice, format_any_value, metric_number_of, examples/tests/docs scaffolding).
  • Updated workspace and downstream crates to depend on and reference observed_utils (replacing prior observed_helpers/local copies).
  • Refreshed dependency/advisory state (lockfile bump for h2, removed stale deny.toml ignores, added .spelling entries).

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
deny.toml Removes stale advisory ignore entries.
crates/observed/FEATURES.md Updates documentation reference to observed_utils::metric_number_of.
crates/observed/examples/support/otel.rs Updates doc comments to reference observed_utils conversions.
crates/observed_utils/src/sensitive_slice.rs Adds SensitiveSlice implementation plus unit tests.
crates/observed_utils/src/otel.rs Adds Value/Text/Severity → OpenTelemetry conversion helpers plus tests.
crates/observed_utils/src/metric_number.rs Provides metric_number_of (including Value::U64) plus tests.
crates/observed_utils/src/lib.rs New crate root, docs, lint config, and public re-exports.
crates/observed_utils/src/format_any_value.rs Adds format_any_value Display adapter for AnyValue plus tests.
crates/observed_utils/README.md Generated crate README for crates.io/docs.rs.
crates/observed_utils/logo.png Adds crate logo asset (Git LFS pointer).
crates/observed_utils/favicon.ico Adds crate favicon asset (Git LFS pointer).
crates/observed_utils/examples/support/taxonomy.rs Example-only taxonomy support module for classification/redaction demos.
crates/observed_utils/examples/sensitive_collection.rs Example demonstrating SensitiveSlice usage in events and processors.
crates/observed_utils/CHANGELOG.md Adds initial changelog for the new crate.
crates/observed_utils/Cargo.toml Defines the new package metadata and dependencies (workspace-inherited).
crates/observed_testing/tests/derive_enrichment.rs Switches metric extraction call site to observed_utils::metric_number_of.
crates/observed_testing/tests/coverage_dyn_sink.rs Switches metric extraction call sites to observed_utils::metric_number_of.
crates/observed_testing/src/mock_processor.rs Switches internal metric extraction to observed_utils::metric_number_of.
crates/observed_testing/src/lib.rs Removes local metric_number module export now sourced from observed_utils.
crates/observed_testing/Cargo.toml Adds observed_utils dependency.
Cargo.toml Adds workspace dependency entries for arrayvec, const-hex, and observed_utils.
Cargo.lock Bumps h2 and adds new dependency resolutions for the new crate and deps.
.spelling Adds new terms used by the moved/added docs.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/observed_utils/src/format_any_value.rs Outdated
@Vaiz
Evgenii (Vaiz) marked this pull request as ready for review August 19, 2026 06:34
Copilot AI review requested due to automatic review settings August 19, 2026 06:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.

Comment thread crates/observed_utils/src/lib.rs
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a document or diagram somewhere that explains the intended end state of crates and dependencies in the observed space? Who is supposed to depend on observed_utils? I would really like to prevent having applications have to depend on something called "observed_utils" - for me, a utils crate is something that should generally be buried in the dependency tree.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

generally speaking, I put here all common functionality that often is required by observed users, but doesn't have place in observed crate itself due to increased risk of breaking changes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I expected that certain percent of crates using observed, also use observed_util instead of reimplementing similar functionality

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this is "ordinary" functionality, it sounds like we should stabilize it, so it could go in main crate. For "ultra-stable" things, maybe the "re-export critical types from observed_core that you never touch" pattern helps avoid accidental breakage? Just speaking in generalizations, I guess it is not something that has a universal pattern we have worked out so far but in general if we expect most users to use a _utils crate it stops having value on its own IMO and might as well get merged into the top-level crate. But probably any detailed analysis needs to be case by case - I know little about observed structure and what is needed by which users. Maybe opportunity for design discussion at future arch meeting?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I would call it experimental. Let's see what people actually use, and where to put it. Right now it solves the problem of reinventing the wheel, and it's good enough for me.

for example, I also plan to add info! shortcut here that defines event struct inline, so you don't need to declare a separate struct for simple events.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Copilot speaking]

Published 4 findings. No finding follows up on an existing discussion thread.

See diagnostics
Diagnostic Value
Cache Miss

Comment thread crates/observed_utils/src/otel.rs
Comment thread crates/observed_utils/src/sensitive_slice.rs
Comment thread crates/observed_utils/src/sensitive_slice.rs Outdated
Comment thread crates/observed_utils/src/metric_number.rs
Copilot AI review requested due to automatic review settings August 19, 2026 11:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.

Suppressed comments (2)

crates/observed_utils/src/sensitive_slice.rs:82

  • The constructor requires T: RedactedDisplay + Sync, but the doc comment only mentions RedactedDisplay. Please document the Sync requirement explicitly so callers aren't surprised by the additional bound.
    /// Collects items from any iterator of references to [`RedactedDisplay`]
    /// implementers.

crates/observed_utils/src/sensitive_slice.rs:22

  • The docs say this stores &dyn RedactedDisplay fat pointers, but the actual stored type is &(dyn RedactedDisplay + Sync). This Sync bound is part of the public contract and should be documented (or the bound removed if it's not required).

This issue also appears on line 81 of the same file.

/// to `N` `&dyn RedactedDisplay` fat pointers in an inline array. This makes

Copilot AI review requested due to automatic review settings August 19, 2026 11:35
@Vaiz

Copy link
Copy Markdown
Contributor Author

🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.

Re the two suppressed comments in review #4971552344, both about SensitiveSlice's undocumented Sync bound — accepted, fixed in 8db85fb3.

Both are correct and they are the same gap seen from two angles. The type documentation said it stores &dyn RedactedDisplay fat pointers, and new said it accepts implementers of RedactedDisplay, but the stored type is &(dyn RedactedDisplay + Sync) and the constructor requires T: RedactedDisplay + Sync. A caller with a non-Sync item type therefore satisfied the documented bound and still failed to compile.

Documented on both the type and the constructor rather than removing the bound. The bound is load-bearing: &(dyn Trait + Sync) is itself Send and Sync, while a bare &dyn Trait is neither, so it is what allows an event struct holding a SensitiveSlice to cross threads. Dropping it would narrow where the type can be used, which is a behaviour change to code this PR only relocates.

Documentation only — no behaviour change, and the generated README is unaffected since these docs live on the type rather than in lib.rs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.

Evgenii (Vaiz) and others added 11 commits August 19, 2026 16:31
Move `observed_helpers` (0.24.0) from the internal ox-sdk monorepo into
this repository, following `observed`, `observed_macros` and
`observed_testing`. It is the optional companion for consumers that
export typed events to OpenTelemetry.

`observed` itself no longer depends on `opentelemetry`, so an exporter
has to convert values on its own. That conversion lives here once rather
than being re-derived in every exporter: `any_value_of`, `otel_value_of`
and `otel_severity_of`. The crate also carries `format_any_value`, which
renders an `AnyValue` in human-readable form instead of its `Debug`
shape, and `SensitiveSlice`, a bounded, type-erased collection of
`RedactedDisplay` references that renders without allocating.

Changes made during the move:

- `any_value_of` and `otel_value_of` handle `Value::U64`. The variant was
  added in 0.24 after this crate was written, so a `u64` dimension used
  to fall through to the `#[non_exhaustive]` guard arm and be exported as
  a debug string. Values past `i64::MAX` still export as a decimal string,
  because neither `AnyValue` nor `opentelemetry::Value` has an unsigned
  variant.
- `metric_number_of` returns to this crate. It was parked in
  `observed_testing` because that was its only consumer here;
  `observed_testing` now uses it from this crate instead of keeping a
  second copy.
- The example uses the `#[path = "support/taxonomy.rs"]` stand-in
  taxonomy the `observed` examples use, instead of the ox-sdk-only
  `microsoft_enterprise_data_taxonomy` crate, and `expect` with a message
  instead of `unwrap`, to satisfy this workspace's `clippy::unwrap_used`
  policy.
- The `#![doc(html_logo_url)]` and `#![cfg_attr(coverage_nightly, ...)]`
  attributes are enabled, matching the convention of the other crates here.
- `arrayvec` and `const-hex` are added to `[workspace.dependencies]`.

Effects:

- A crate that emits typed events and exports them no longer has to write
  its own `Value` mapping, and the mapping is governed by this
  repository's gates.
- `observed_testing` gains a dependency on `observed_helpers`; the two
  copies of `metric_number_of` can no longer drift apart.
- Telemetry helper changes no longer need a manual transfer between two
  repositories.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- spellcheck: use the repo's `implementers` spelling and the US `signaled`
  in `SensitiveSlice::new` docs (both were rejected by cargo-spellcheck).
- external types: approve `opentelemetry::logs::record::Severity`, the
  canonical path cargo-check-external-types reports, instead of the
  `opentelemetry::logs::Severity` re-export path.
- coverage: mark every `mod tests` with
  `#[cfg_attr(coverage_nightly, coverage(off))]`, matching the convention in
  `observed`. This also consumes `feature(coverage_attribute)`, which was
  declared but unused and failed the coverage build under `-D warnings`.
- coverage/mutants: cover the paths no test reached - `AnyValue::Map`
  rendering (which also kills the three surviving `i > 0` mutants in
  `DisplayAnyValue::fmt`), `metric_number_of` on `Value::U64`,
  `otel_value_of` on `Bool`/`F64`/`BoolArray`/`F64Array`, `Text::Shared`,
  and the `Debug`/`Clone` impls of `SensitiveSlice`.

The four remaining uncovered lines are the intentionally unreachable
wildcard arms that guard the foreign `#[non_exhaustive]` enums; they are
kept deliberately and are not removed to satisfy the gate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… coverage

The coverage gate reported observed_helpers at 96.8% against the 100%
per-package threshold. The four uncovered lines are the wildcard arms that
guard the foreign `#[non_exhaustive]` enums (`Value`, `Text`, `AnyValue`):
no variant that exists today can reach them, and llvm-cov counts an arm
that is never taken as an uncovered line.

The guards stay - deleting them would turn an upstream variant addition
into a compile error here, or silently drop the value. Instead the three
dispatch functions carry `#[cfg_attr(coverage_nightly, coverage(off))]`,
and the logic they delegate to is pulled out into helpers that remain
measured: `unsigned_any_value` / `unsigned_otel_value` (the u64-past-i64::MAX
string export), `list_of`, and `write_list` / `write_map`. Mutation testing
still applies to the excluded dispatches.
`cargo deny check` emitted `advisory-not-detected` for both:

- RUSTSEC-2025-0141 (bincode 1.3.3): bincode is no longer in `Cargo.lock`
  at all.
- RUSTSEC-2026-0173 (proc-macro-error2): proc-macro-error2 2.0.1 is still
  in the graph, but the advisory no longer matches it.

The quick-xml ignores stay: quick-xml 0.39.4 is still pulled in through
the Azure SDK and those advisories still match. `cargo deny check all`
now reports advisories, bans, licenses and sources ok with no
`advisory-not-detected` warnings.
…etection

- `SensitiveSlice::new` drops the post-loop `iter.next()` probe. It was
  unreachable: the `for` loop exits normally only after `next()` returned
  `None`, so the extra call could report `Some` only for an iterator that
  resumes after `None`. The loop already detects overflow correctly on its
  own, because `is_full()` is checked BEFORE the push, so the `N+1`-th item
  is pulled, observed and discarded. The comment now explains that instead
  of narrating the removed call.
- The example's module doc said `SensitiveCollection`, a type that does not
  exist; the example uses `SensitiveSlice`.
- `examples/support/taxonomy.rs` said it was shared by the `observed`
  crate's examples. It is a per-crate copy, because an example cannot
  include a file outside its own package directory; the doc now says so.
- `format_any_value.rs` and `sensitive_slice.rs` gain `//!` module headers,
  matching `otel.rs`.
- The coverage-exclusion comments overstated what stays measured. They now
  state exactly what the exclusion covers: the inline scalar and `Bytes`
  rendering is excluded along with the guard arm and is pinned by tests and
  mutation testing rather than by the coverage gate. `string_value_of`
  delegates nothing at all, so its comment no longer claims otherwise.
`cargo-spellcheck` rejected two words introduced by the module docs added
in 722f308: "recursing" in `format_any_value.rs` and "redactable" in
`sensitive_slice.rs`. Both are correct technical English, so they are added
to `.spelling` rather than the prose being reworded around the checker.

They go into the same trailing block as the other `observed`-family terms,
keeping that block's alphabetical order. Verified with
`cargo spellcheck --cfg spellcheck.toml check --code 1`, which now reports
no findings.
Per review: `observed_utils` reads more idiomatically, and since nothing
is published yet the crate starts its own version line at 0.1.0 rather
than inheriting `observed`'s 0.24.0.

Renamed in place: the directory, the package name, the `repository` and
`html_logo_url` / `html_favicon_url` attributes, both `observed_testing`
call sites and its dependency entry, the `[workspace.dependencies]` entry,
the two prose references under `crates/observed` (FEATURES.md and the
`observed` OTel example), and the lockfile. The README is regenerated by
cargo-doc2readme, not hand-edited. Git records every file as a rename, so
the move itself carries no content change.

The shorter crate name lets the `html_favicon_url` attribute fit on one
line again, which is the only formatting change.

Gates on the renamed crate: tests, clippy -D warnings, doc, both rustfmt
configs, doc2readme --check, spellcheck, and `cargo deny check all` are
clean; coverage is 100 % with no uncovered lines, and `cargo mutants`
over the package reports no surviving mutants.
…laim

Two fixes from the CI run and review of the rename:

- `anvil-cargo-sort` failed on all four `pr-fast` legs and on
  `static-analysis` with "Dependencies for oxidizer are not sorted". The
  rename left the entry in the slot `observed_helpers` used to occupy, which
  sorted before `observed_macros`; `observed_utils` sorts after
  `observed_testing`. Moved. `cargo sort --workspace --grouped --check
  --check-format` now passes.
- The `format_any_value` module doc claimed rendering "allocates nothing
  beyond what the formatter itself needs". That is wrong for the `Bytes`
  arm: `const_hex::encode` returns an owned `String`. The doc now says that
  every arm writes straight to the formatter except `Bytes`, which builds
  one temporary hex string.

The allocation itself is left alone. Writing hex directly into the formatter
would change the behaviour of code this PR only moves, and the crate enables
`const-hex`'s `alloc` feature for exactly this call.
Per review, the `AnyValue::Bytes` arm no longer builds a temporary `String`.
`const_hex::encode` allocates; `const_hex::display` returns a `Display`
adapter that writes through const-hex's own encoder straight into the
formatter. Every arm of the renderer now writes directly, so the module doc
can state plainly that rendering allocates nothing.

`display` is not gated behind const-hex's `alloc` feature, and `encode` was
the crate's only use of it, so the feature is dropped. That also removes
`proptest`, `rand_xorshift` and `unarray` from `Cargo.lock`: const-hex
enables proptest through `alloc`, and nothing else in the workspace pulled
it in.

Gates: tests, clippy -D warnings, doc, both rustfmt configs, doc2readme
--check, spellcheck, cargo-sort, cargo-machete and `cargo deny check all`
are clean; coverage stays at 100 % with no uncovered lines, and
`cargo mutants` over the file reports no surviving mutants.
Review found three public-doc claims that the implementation does not
support as written.

`any_value_of` and `otel_value_of` described the result as the value's
OpenTelemetry counterpart, but a `Value::U64` has no single output type:
up to `i64::MAX` it becomes an integer, past it a decimal string. That
boundary was documented only on the private helpers, which do not reach
rustdoc. It is now on both public functions and in the crate overview.

`SensitiveSlice`'s `N` was documented only as an upper bound, while
`new` asserts `N > 0` at compile time. The lower bound is now stated on
the type parameter and on the constructor.

The module claimed items are "never rendered in the clear by this
module" and the crate claimed rendering "without allocating". Both are
absolute where the behaviour is delegated: items are rendered by their
own `RedactedDisplay` implementation under the caller's `Redactor`,
which may pass values through, and an item may allocate. The wording now
scopes both promises to what the wrapper controls - inline storage of
the collection - and names the delegation.

Documentation only; no behaviour change. The README is regenerated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The type documentation said it stores `&dyn RedactedDisplay` fat
pointers and that `new` accepts implementers of `RedactedDisplay`. Both
understate the contract: the stored type is `&(dyn RedactedDisplay +
Sync)` and the constructor requires `T: RedactedDisplay + Sync`, so a
caller with a non-`Sync` item type meets the documented bound and still
fails to compile.

The bound stays. `&(dyn Trait + Sync)` is itself `Send` and `Sync` while
a bare `&dyn Trait` is neither, so it is what lets an event struct
holding a `SensitiveSlice` cross threads. The documentation now states
it on the type and on the constructor.

Documentation only; no behaviour change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 19, 2026 15:37
"opentelemetry::common::Value",
"opentelemetry::logs::record::AnyValue",
"opentelemetry::logs::record::Severity",
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 This crate combines independently usable OpenTelemetry conversion, metric extraction, formatting, and privacy collection APIs. A SensitiveSlice consumer therefore compiles the OpenTelemetry dependencies, while conversion users compile the privacy stack. Please split the independently usable surfaces (for example, a focused OpenTelemetry crate and a privacy collection crate) or otherwise avoid forcing unrelated dependency sets.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Requesting changes for the lossy integer-to-float metric conversion. The crate-boundary concern is secondary.

#[test]
fn numeric_values_convert() {
assert_eq!(metric_number_of(&Value::from(42_i64)), Some(42.0));
assert_eq!(metric_number_of(&Value::from(7_u64)), Some(7.0));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 The implementation still converts i64 and u64 to f64; this small-value test misses the boundary where adjacent counters above 2^53 collapse to the same observation. Please preserve the numeric kind until instrument selection and add boundary tests around 2^53.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.

This point was already raised and answered on this PR — see #677 (comment) for the reasoning (the numerics are correct and acknowledged by the #[expect(clippy::cast_precision_loss, ...)] at both arms; changing the return type is a redesign of a helper this PR only relocates, so it is follow-up material along with the 2^53 boundary test). Flagging it to the PR owner rather than re-deciding it here.

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.

6 participants