Skip to content

Add runtime resource attributes, span status/kind, and fix ODR singletons#13

Open
proffalken wants to merge 7 commits intomainfrom
feat/runtime-resource-attrs-and-span-status
Open

Add runtime resource attributes, span status/kind, and fix ODR singletons#13
proffalken wants to merge 7 commits intomainfrom
feat/runtime-resource-attrs-and-span-status

Conversation

@proffalken
Copy link
Owner

@proffalken proffalken commented Feb 9, 2026

Summary

  • Fix singleton ODR violation: All singleton accessor functions (defaultResource, currentTraceContext, tracerConfig, etc.) were static inline, giving each translation unit its own copy. Changed to inline so state is shared across TUs — without this, runtime resource attributes set in one .cpp were invisible to signal builders in others.

  • Runtime resource attributes: Added buildResourceAttributes() that merges defaultResource() values with compile-time fallbacks. All signal types (traces, metrics, logs) now use this, allowing callers to set service.name, service.namespace, service.instance.id etc. at runtime.

  • Span status and kind API: Added setStatus(code, msg), setError(msg), setOk() for OTLP StatusCode and setKind(kind) for SpanKind. Previously kind was hardcoded to SERVER and status was always UNSET, preventing proper error detection and span classification in backends like Dash0/Grafana.

Test plan

  • Builds successfully on ESP32 and ESP8266
  • Verified runtime resource attributes appear on traces, logs, and metrics in Dash0
  • Verified span status ERROR shows correctly for error spans
  • Verified span kind INTERNAL/CONSUMER classification works
  • Backward compatible — existing code that doesn't call the new APIs gets the same defaults as before

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Spans can record status (code + optional message), mark explicit success, report errors, and set an explicit span kind.
    • New exported span-kind and status-code constants for clearer telemetry semantics.
  • Improvements

    • Resource attribute assembly consolidated for more consistent telemetry exports.
    • Span serialization now emits status only when set, reducing noise in exported spans.

…tons

Three related improvements:

1. Fix singleton ODR violation across translation units

   All singleton accessor functions (defaultResource, currentTraceContext,
   tracerConfig, metricsScopeConfig, defaultMetricLabels, logScopeConfig,
   defaultLabels) were declared `static inline`, giving each .cpp file its
   own separate copy of the static local variable. This meant values set
   in one translation unit (e.g. resource attributes) were invisible to
   signal builders compiled in other translation units.

   Changed from `static inline` to `inline` so C++ guarantees a single
   shared instance across all translation units.

2. Runtime resource attribute support

   Added buildResourceAttributes() helper that merges runtime
   defaultResource() values with compile-time fallbacks. All three signal
   types (traces, metrics, logs) now use this instead of hardcoded
   addResAttr() calls. This allows callers to set service.name,
   service.namespace, service.instance.id etc. at runtime via
   defaultResource().set() and have them appear on all telemetry.

3. Span status and kind API

   Added setStatus(code, message), setError(message), setOk() for OTLP
   StatusCode (UNSET=0, OK=1, ERROR=2) and setKind(kind) for SpanKind
   (INTERNAL=1, SERVER=2, CLIENT=3, PRODUCER=4, CONSUMER=5). Previously
   kind was hardcoded to SERVER and status was always UNSET, which
   prevented proper error detection and span classification in backends.

   Also fixed the move constructor/assignment to transfer the new fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 9, 2026 18:15
@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Removed static internal linkage from several inline accessors, added buildResourceAttributes(...) to merge runtime resource attributes with compile-time fallbacks, and extended Span with persistent kind and OTLP-style status fields plus new setters and adjusted serialization.

Changes

Cohort / File(s) Summary
Linkage changes (accessors)
include/OtelDefaults.h, include/OtelLogger.h, include/OtelMetrics.h, include/OtelTracer.h
Dropped static from multiple static inline accessors (e.g. defaultResource, logScopeConfig, defaultLabels, metricsScopeConfig, defaultMetricLabels, currentTraceContext, tracerConfig) — kept inline but changed linkage/visibility.
Resource attributes consolidation
include/OtelDefaults.h, include/OtelLogger.h, include/OtelTracer.h, src/OtelMetrics.cpp
Added inline void buildResourceAttributes(JsonArray& attrs, const String& fallbackServiceName, const String& fallbackInstanceId, const String& fallbackHostName) which emits fallback keys (service.name, service.instance.id, host.name) when missing, then appends runtime attributes; replaced direct addResAttr(...) calls with this helper.
Span API, state & serialization
include/OtelTracer.h
Added Span members kind_, statusCode_, statusMessage_; updated move ctor/assignment to propagate them; added public methods setStatus(int,const String&), setError(const String&), setOk(), setKind(int); End()/serialization now conditionally emits status and uses kind_ for span kind.
Metrics usage change
src/OtelMetrics.cpp
Replaced explicit addResAttr(...) calls with buildResourceAttributes(...) using default service/instance/host fallbacks.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Span as Span
    participant Serializer as SpanSerializer
    participant Export as Exporter

    App->>Span: setStatus(code, "message")
    Span->>Span: store statusCode_ and statusMessage_
    App->>Span: setKind(kind)
    App->>Span: end()
    Span->>Serializer: request serialize
    Serializer->>Serializer: if statusCode_ != UNSET
    alt status set
        Serializer->>Export: emit status { code, message }
    end
    Serializer->>Export: emit kind_ and other span fields
Loading
sequenceDiagram
    participant Init as Initialiser
    participant Logger as Logger/Metrics/Tracer
    participant Helper as buildResourceAttributes()
    participant Runtime as RuntimeResource
    participant Attrs as AttributesArray

    Init->>Logger: prepare resource attributes (fallbacks)
    Logger->>Helper: buildResourceAttributes(attrs, svc, inst, host)
    Helper->>Runtime: read runtime resource attributes
    Helper->>Helper: insert fallback svc/inst/host if missing
    Helper->>Attrs: append runtime attributes (overrides allowed)
    Logger->>Attrs: use merged attributes for export
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through headers, tidy and bright,
I nudged a static out into the light.
I stitched fallback names where runtime was thin,
Gave spans a status, and a kind tucked in.
A little rabbit cheer — hop, stamp, and grin! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes: ODR singleton fixes, runtime resource attributes, and span status/kind API additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/runtime-resource-attrs-and-span-status

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
include/OtelTracer.h (2)

417-431: Consider using enum class for type safety.

SpanKind and StatusCode as bare constexpr int in a namespace work, but nothing prevents a caller from passing a StatusCode value to setKind() or vice versa. An enum class with an explicit static_cast<int> at serialisation time would catch such mix-ups at compile time.

Not blocking — the current approach is pragmatic for embedded targets where JSON serialisation with int is convenient.


736-739: Default kind_ is SpanKind::SERVER — verify this is the desired default for your use-case.

Most OTLP SDKs default to INTERNAL (1) rather than SERVER (2), since a generic span is not necessarily serving an inbound request. On an embedded device acting as an HTTP server this is fine, but if most of your spans represent internal work or outbound calls, INTERNAL may be a more accurate fallback.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@include/OtelTracer.h`:
- Around line 650-657: The status message is currently serialized whenever
statusCode_ is non-zero, which can emit a message for OK (1); change the
serialization logic in the span status block so the "message" field is only
added when statusCode_ == 2 (ERROR). Specifically, in the code that builds
JsonObject status (using statusCode_ and statusMessage_), guard the assignment
of status["message"] with a check that statusCode_ == 2 and
statusMessage_.length() > 0 so only error statuses include the message.
🧹 Nitpick comments (2)
include/OtelDefaults.h (1)

152-155: static inline is inconsistent with the rest of the PR's ODR fixes — should be just inline.

The entire PR rationale is moving from static inline to inline for functions in headers. While buildResourceAttributes has no static-local state (so there's no singleton bug), marking it static inline still generates a separate copy in every translation unit that includes this header, needlessly increasing code size on memory-constrained embedded targets.

Suggested fix
-static inline void buildResourceAttributes(JsonArray& attrs,
+inline void buildResourceAttributes(JsonArray& attrs,
     const String& fallbackServiceName,
     const String& fallbackInstanceId,
     const String& fallbackHostName)
include/OtelTracer.h (1)

495-514: Consider using enum/enum class instead of raw int for status codes and span kinds.

The magic integers (0/1/2 for status, 1–5 for kind) are easy to misuse. A lightweight enum class would provide compile-time safety at zero runtime cost and make call sites self-documenting:

span.setKind(OTel::SpanKind::CONSUMER);
span.setStatus(OTel::StatusCode::ERROR, "timeout");

This would also let you validate inputs or at least constrain the type, rather than accepting any int.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves OpenTelemetry signal generation for embedded targets by (1) making header-defined “singleton” state truly shared across translation units, (2) enabling runtime-configurable resource attributes across traces/metrics/logs, and (3) adding span kind/status APIs so exported spans can be properly classified and marked as errors.

Changes:

  • Fixes ODR/state duplication by switching several static inline singleton accessors to inline (shared across TUs).
  • Introduces buildResourceAttributes() and wires it into traces/metrics/logs to merge runtime defaultResource() attributes with compile-time fallbacks.
  • Adds span status (setStatus/setOk/setError) and span kind (setKind) support and serializes them into OTLP JSON.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/OtelMetrics.cpp Switches metrics resource attribute emission to use the new merged runtime+fallback resource builder.
include/OtelTracer.h Makes trace context/config singletons TU-shared; adds span kind/status API and serialization.
include/OtelMetrics.h Makes metrics scope config and default label singleton maps TU-shared.
include/OtelLogger.h Makes log scope config and default label singleton maps TU-shared; uses merged resource attributes builder.
include/OtelDefaults.h Makes defaultResource() TU-shared and adds buildResourceAttributes() helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 9, 2026 20:34
proffalken and others added 2 commits February 9, 2026 20:35
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

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 5 out of 5 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

include/OtelTracer.h:656

  • setStatus(code, message) accepts a message for any status code, but end() only serializes status.message when code == 2 (ERROR). This makes setStatus(OK, "...") silently drop the message. Either serialize the message whenever it’s non-empty, or clear/ignore the message in setStatus() for non-ERROR codes to keep the API behavior consistent.
            switch (at.type) {
              case Type::Str:  v["stringValue"] = at.s; break;
              case Type::Int:  v["intValue"]    = at.i; break;
              case Type::Dbl:  v["doubleValue"] = at.d; break;
              case Type::Bool: v["boolValue"]   = at.b; break;
            }
          }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@include/OtelTracer.h`:
- Around line 495-508: The current Span::setStatus(int code, const String&
message = "") clamps out-of-range positive codes to ERROR (statusCode_ = 2),
which can falsely surface issues; change the clamp so any invalid code (code < 0
or code > 2) is set to UNSET (statusCode_ = 0) instead, keep assigning
statusMessage_, and add a debug/logging call (using the project's debug logger
or Serial) inside the invalid-code branch to record the original invalid value
for diagnostics; update the branches in setStatus to reflect this new behavior
and ensure statusCode_ and statusMessage_ are still returned via the Span&
result.
- Line 599: Remove the two unused helper functions mentioned: the
namespace-level addResAttr and the private Span::addResAttr, since
buildResourceAttributes already uses serializeKeyValue; locate the functions by
name (addResAttr and the class method Span::addResAttr) and delete their
declarations and definitions, plus any associated forward declarations or
includes that become unused after removal, then run a build to ensure no
references remain and update any comments mentioning these helpers.
🧹 Nitpick comments (1)
include/OtelTracer.h (1)

727-730: Consider named constants or an enum class for span kind and status code.

Raw int magic numbers are easy to misuse (e.g., passing a kind where a status is expected). Even lightweight constants would improve self-documentation and enable compiler-assisted checks:

// At namespace level:
namespace SpanKind   { constexpr int INTERNAL=1, SERVER=2, CLIENT=3, PRODUCER=4, CONSUMER=5; }
namespace StatusCode { constexpr int UNSET=0, OK=1, ERROR=2; }

This keeps the wire format as int but makes call sites like span.setKind(SpanKind::CLIENT) and span.setStatus(StatusCode::ERROR, "timeout") self-explanatory.

proffalken and others added 2 commits February 10, 2026 10:07
- Add SpanKind and StatusCode named constants to replace magic ints
- Validate setStatus/setKind inputs, clamp invalid codes to UNSET
- Only serialize status message for ERROR per OTLP spec
- Change buildResourceAttributes from static inline to inline
- Use pre-constructed static String keys to avoid heap allocations
- Remove dead addResAttr helpers (namespace-level and Span private)
- Update include comments that referenced removed helpers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Existing users may call addResAttr() directly. Rather than removing it
outright, mark it [[deprecated]] pointing to buildResourceAttributes().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 10, 2026 10:23
Copy link
Contributor

Copilot AI left a comment

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 5 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Use SpanKind::SERVER and StatusCode::UNSET named constants for default
  member initializers instead of magic numbers
- Fix misleading "keeping SERVER" warning in setKind() to report actual
  previous kind value
- Guard setStatus()/setKind() warnings behind DEBUG macro instead of
  unconditional Serial.printf
- Fix buildResourceAttributes() indentation to match file's 2-space style

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant