Skip to content

feat(plugins): add OTLP mark parity to dynamic plugins - #779

Draft
bbednarski9 wants to merge 4 commits into
mainfrom
bbednarski/otel-logs-metrics
Draft

feat(plugins): add OTLP mark parity to dynamic plugins#779
bbednarski9 wants to merge 4 commits into
mainfrom
bbednarski/otel-logs-metrics

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Overview

Complete dynamic-plugin parity for typed OTLP log and metric marks and document the full feature.

This PR is independently rebased on main after #780. It contains only the dynamic-plugin, worker-SDK, and documentation layer; it has no branch dependency on the Python, Node.js, or C FFI/Go follow-up PRs. Review and merge it on its own readiness.

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

  • Extend Relay 0.8 native ABI v4 with append-only emit_mark_v2 support for data schemas and severity; this does not introduce ABI v5.
  • Preserve v3/v2 fallbacks and compatibility with the earlier v4 prefix.
  • Extend the gRPC worker protocol additively with typed data-schema and severity fields.
  • Add metric helpers to the Rust native SDK, Rust worker SDK, and Python worker SDK.
  • Add native and worker fixtures that transport Tokenomics-shaped metric marks through the generic Relay contract.
  • Document metric routing, severity, endpoint derivation, configuration version 4, dynamic-plugin contracts, SDK limitations, and migration guidance.

Validation:

  • Native plugin SDK: 51 passed.
  • Worker protocol: 7 passed; Rust worker SDK suite passed.
  • Python worker SDK: 119 passed.
  • Native dynamic-plugin integration: 32 passed.
  • gRPC worker-plugin integration: 31 passed.
  • Fern validation and strict broken-link checks passed.
  • Rust and Python formatting/lint checks passed.

Breaking changes: none for released native ABI versions. Relay 0.8 continues to use ABI v4.

Where should the reviewer start?

Start with crates/plugin/src/lib.rs for ABI-v4 negotiation and mark emission, then crates/worker-proto/proto/nemo/relay/worker/v1/plugin_worker.proto and crates/core/src/plugin/dynamic/worker.rs. The user-facing contract is documented in docs/configure-plugins/observability/opentelemetry.mdx.

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 73b5804b-2561-43be-9a96-5e07dcb2ccf8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:XXL PR is very large Feature a new feature lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code labels Aug 13, 2026
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

License Diff

Compared against origin/main.

Lockfile license changes

Lockfile License Changes

Rust

Added

  • None

Removed

  • None

Updated/Changed

  • None

Node

Added

  • None

Removed

  • None

Updated/Changed

  • None

Python

Added

  • None

Removed

  • None

Updated/Changed

  • None
Status output
[license-diff] selected languages: rust, node, python
[license-diff] generating current inventory
[license-diff] current: generating Rust inventory
[license-diff] current: Rust inventory complete (448 packages)
[license-diff] current: generating Node inventory
[license-diff] current: Node inventory complete (367 packages)
[license-diff] current: generating Python inventory
[license-diff] current: Python inventory complete (105 packages)
[license-diff] current inventory complete
[license-diff] checking out base ref origin/main into a temporary worktree
[license-diff] base: generating Rust inventory
[license-diff] base: Rust inventory complete (448 packages)
[license-diff] base: generating Node inventory
[license-diff] base: Node inventory complete (367 packages)
[license-diff] base: generating Python inventory
[license-diff] base: Python inventory complete (105 packages)
[license-diff] base inventory complete
[license-diff] removing temporary base worktree
[license-diff] comparing inventories
[license-diff] rendering Markdown output
[license-diff] done

@github-actions

Copy link
Copy Markdown

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.05151% with 980 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.75%. Comparing base (8ffc6d1) to head (174166b).

Files with missing lines Patch % Lines
crates/core/src/observability/plugin_component.rs 70.59% 260 Missing ⚠️
crates/core/src/observability/otel_metrics.rs 76.47% 148 Missing ⚠️
crates/ffi/src/api/observability.rs 64.81% 120 Missing ⚠️
crates/core/src/observability/otel_logs.rs 84.96% 85 Missing ⚠️
go/nemo_relay/nemo_relay.go 80.81% 66 Missing ⚠️
crates/python/src/py_types/core.rs 50.43% 57 Missing ⚠️
crates/python/src/py_types/observability.rs 74.44% 57 Missing ⚠️
crates/ffi/src/api/scope.rs 76.11% 43 Missing ⚠️
crates/core/src/observability/otel_signal.rs 80.73% 37 Missing ⚠️
crates/worker/src/lib.rs 54.10% 28 Missing ⚠️
... and 12 more

❌ Your patch check has failed because the patch coverage (78.05%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project check has failed because the head coverage (93.96%) is below the target coverage (95.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #779      +/-   ##
==========================================
- Coverage   94.44%   93.75%   -0.69%     
==========================================
  Files         323      326       +3     
  Lines      101387   105748    +4361     
  Branches      113      118       +5     
==========================================
+ Hits        95749    99137    +3388     
- Misses       5638     6610     +972     
- Partials        0        1       +1     
Components Coverage Δ
Rust Runtime 94.96% <77.80%> (-0.96%) ⬇️
Shared DTO Types 96.98% <92.08%> (-2.12%) ⬇️
CLI 91.96% <100.00%> (+0.01%) ⬆️
Go Binding 93.09% <74.63%> (-2.01%) ⬇️
Python Binding 93.96% <74.00%> (-1.17%) ⬇️
Node Binding 98.11% <100.00%> (-0.24%) ⬇️
Dynamic Plugin SDKs 94.78% <85.77%> (-0.81%) ⬇️
Files with missing lines Coverage Δ
crates/cli/src/plugins/editor_model.rs 96.81% <100.00%> (-0.02%) ⬇️
crates/cli/src/plugins/mod.rs 91.85% <100.00%> (+0.34%) ⬆️
crates/core/src/config_editor.rs 88.00% <ø> (ø)
crates/core/src/observability/mod.rs 97.64% <ø> (ø)
crates/core/src/plugin.rs 95.30% <100.00%> (+0.05%) ⬆️
crates/ffi/src/api/mod.rs 84.88% <ø> (ø)
crates/node/observability.js 100.00% <100.00%> (ø)
crates/python/src/py_types/events.rs 92.96% <100.00%> (+0.59%) ⬆️
crates/python/src/py_types/mod.rs 100.00% <100.00%> (ø)
go/nemo_relay/callbacks.go 96.16% <ø> (ø)
... and 27 more

... and 6 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8ffc6d1...174166b. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 54dab2b to f168872 Compare August 13, 2026 03:58
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from f168872 to b5b3253 Compare August 13, 2026 04:34
@bbednarski9 bbednarski9 changed the title feat(observability): add OTLP logs and metrics support feat(plugins): add OTLP mark parity to dynamic plugins Aug 13, 2026
@bbednarski9
bbednarski9 changed the base branch from main to bbednarski/otel-signals-ffi-go August 13, 2026 04:35
@github-actions github-actions Bot added size:XL PR is extra large and removed size:XXL PR is very large lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code labels Aug 13, 2026
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-signals-ffi-go branch from 3bde200 to 8d9ab26 Compare August 13, 2026 19:17
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from b5b3253 to 79e2d9c Compare August 13, 2026 19:17
@bbednarski9
bbednarski9 changed the base branch from bbednarski/otel-signals-ffi-go to main August 13, 2026 20:01
@github-actions github-actions Bot removed the size:XL PR is extra large label Aug 13, 2026
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 672caf0 to 6f67550 Compare August 13, 2026 21:03
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 6f67550 to 9b75bf5 Compare August 13, 2026 21:22
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 9b75bf5 to 876da53 Compare August 13, 2026 21:29
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 876da53 to 3de4f24 Compare August 13, 2026 21:37
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 3de4f24 to 81fc1bc Compare August 13, 2026 21:52
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 81fc1bc to 174166b Compare August 13, 2026 22:18
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 174166b to 463f087 Compare August 13, 2026 22:56
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 463f087 to d2efe38 Compare August 13, 2026 23:07
rapids-bot Bot pushed a commit that referenced this pull request Aug 14, 2026
#### Overview

Add the Rust source-of-truth APIs and runtime support for independent OTLP log and metric pipelines.

This is stack PR 1 of 5.

##### Stack navigation

1. **[#780 — Core runtime, shared event model, config v4, and CLI](#780) — this PR**
2. [#781 — Python and PyO3 bindings](#781)
3. [#782 — Node.js and N-API bindings](#782)
4. [#783 — C FFI and Go bindings](#783)
5. [#779 — Dynamic native/gRPC plugins and consolidated docs](#779)

**Position:** 1 of 5 · **GitHub base:** `main` · **Logical predecessor:** `main` · **Layer-only diff:** [compare branches](main...bbednarski/otel-signals-core) · **Next:** [#781](#781)

All five PRs target `main`. Their branches are cumulative: this PR includes every preceding layer until those PRs merge and this branch is rebased onto the updated `main`. The GitHub **Files changed** tab therefore shows the cumulative diff. Use the layer-only comparison above to review only the code introduced by this layer.

Review and merge in order: [#780](#780) → [#781](#781) → [#782](#782) → [#783](#783) → [#779](#779). After each merge, rebase the next branch onto the updated `main`; its PR remains targeted at `main` and its cumulative diff contracts to the remaining layers.

- [x] I confirm this contribution is my own work, or I have the right to submit it under the project's license.
- [x] I searched existing issues and open pull requests, and this does not duplicate existing work.

#### Details

- Add typed log severity, metric measurements, metric envelopes, optional mark `data_schema` and `severity`, and the Rust `metric` API.
- Keep `MetricMeasurement` as the serde-facing wire DTO. Each metric envelope is parsed atomically into `ValidatedMetricMeasurement` values before it reaches the OTLP metrics exporter.
- Make validation local to the parsed domain types: `InstrumentName` enforces the OTel instrument-name grammar and case-insensitive canonical key; `MetricValue` carries only `U64`, `I64`, or finite `F64`; `HistogramBoundaries` checks finite, strictly increasing, bounded buckets; and `MetricAttributes` owns scalar or homogeneous primitive-array parsing.
- Build `InstrumentDescriptor` from the typed fields and enforce supported `kind × value` combinations once. Envelope consistency groups descriptors by canonical name and requires stable kind and unit; description and histogram boundaries are retained as non-identifying advisory fields.
- Classify reserved metric marks before signal export. An invalid envelope is rejected atomically with a runtime diagnostic; a valid envelope carries only typed measurements into the metric registry and recorder.
- Use the validated descriptor to construct cached OTLP instruments and convert typed attributes directly to OpenTelemetry values. The recorder contains no JSON validation branches; impossible `kind × value` pairs are guarded as internal invariants.
- Export sanitized non-metric marks as structured OTLP logs with severity filtering and scope correlation.
- Add observability config version 4, signal-specific endpoint derivation and validation, lifecycle handling, diagnostics, layering, generated schema, and CLI editor support.
- Continue accepting version 3 as trace-only and preserve existing trace behavior for non-metric marks.
- Include minimal internal Python, Node.js, and FFI test bridges so this base commit remains workspace-buildable; their public APIs are reviewed in later stack PRs.

### Metric data-model architecture

```mermaid
flowchart LR
  subgraph Wire["Untrusted wire / serde boundary"]
    JSON["Metric mark JSON"]
    Envelope["MetricEnvelope"]
    WireMeasurement["MetricMeasurement<br/>name · kind · value_type · JSON value<br/>unit · description · boundaries · JSON attributes"]
    JSON --> Envelope --> WireMeasurement
  end

  subgraph Parse["Single parsing and validation boundary"]
    Convert["ValidatedMetricMeasurement::try_from(&MetricMeasurement)"]
    Name["InstrumentName<br/>OTel grammar + canonical name"]
    Value["MetricValue<br/>U64 | I64 | F64(FiniteF64)"]
    Descriptor["InstrumentDescriptor<br/>name · kind · unit · description · boundaries"]
    Bounds["HistogramBoundaries<br/>finite · strictly increasing · ≤ limit"]
    Attributes["MetricAttributes<br/>BTreeMap&lt;String, AttributeValue&gt;"]
    AttrValue["AttributeValue<br/>scalar or homogeneous typed array"]

    WireMeasurement --> Convert
    Convert --> Name
    Convert --> Value
    Convert --> Bounds
    Bounds --> Descriptor
    Name --> Descriptor
    Convert --> Attributes
    Attributes --> AttrValue
  end

  subgraph EnvelopePolicy["Envelope-level policy"]
    Parsed["Vec&lt;ValidatedMetricMeasurement&gt;"]
    Consistency["Canonical-name descriptor consistency<br/>kind + unit + value type<br/>(description/boundaries advisory)"]
    Convert --> Parsed --> Consistency
  end

  subgraph Export["OTLP exporter: typed inputs only"]
    Classify["MetricMarkClassification::Valid"]
    Registry["Instrument registry / cached OTLP instrument"]
    Record["record_measurement<br/>matches typed MetricValue"]
    OTLP["OpenTelemetry metrics export"]

    Consistency --> Classify --> Registry --> Record --> OTLP
    Attributes --> Record
    Descriptor --> Registry
    Value --> Record
  end

  Invalid["MetricMarkClassification::Invalid<br/>parse/validation error"]
  Convert -. failure .-> Invalid
  Consistency -. failure .-> Invalid
```

Validation:

- `cargo fmt --all`
- `cargo clippy --workspace --all-targets -- -D warnings`
- Focused metric-model and OTLP metrics tests, plus the full Rust workspace, Python, Node.js, and Go/FFI validation matrix, passed during implementation.
- `uv run pre-commit run --all-files` passed except the repository's `python-worker-proto-check`, which requires the unavailable `just` executable.

Breaking changes: none for version 3 trace configuration or existing trace subscriber APIs.

#### Where should the reviewer start?

Start with `crates/types/src/api/event.rs`: it contains the wire DTO, parsed-domain types, and atomic envelope parser. Then review `crates/core/src/observability/otel_signal.rs` for classification and `crates/core/src/observability/otel_metrics.rs` for typed instrument registration and recording. Configuration versioning and endpoint derivation live in `crates/core/src/observability/plugin_component.rs`.

#### Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

- Relates to: none


## Summary by CodeRabbit

* **New Features**
  * Added OpenTelemetry log and metric exporting with configurable endpoints, transports, batching, filtering, resource metadata, and delivery controls.
  * Added validated metric events with measurements, attributes, histograms, limits, and temporality options.
  * Added typed log severity and data schema support for emitted events.
  * Observability configuration now defaults to version 4 while retaining version 3 trace-only compatibility.

* **Bug Fixes**
  * Improved handling and diagnostics for invalid events, export failures, queue drops, and shutdown errors.
  * Preserved metadata and severity when emitting tool and LLM events.

Authors:
  - Bryan Bednarski (https://github.com/bbednarski9)

Approvers:
  - Eric Evans II (https://github.com/ericevans-nv)
  - Maryam Najafian (https://github.com/mnajafian-nv)
  - Will Killian (https://github.com/willkill07)

URL: #780
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9
bbednarski9 force-pushed the bbednarski/otel-logs-metrics branch from 479e4b4 to e00579e Compare August 15, 2026 05:18
@github-actions github-actions Bot added size:XL PR is extra large and removed size:XXL PR is very large lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code labels Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature a new feature lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code size:XL PR is extra large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant