Skip to content

feat(context): prototype context-scoped attributes (#8532) - #8947

Open
rooks-ns wants to merge 3 commits into
open-telemetry:mainfrom
rooks-ns:feat/context-scoped-attributes
Open

rooks-ns wants to merge 3 commits into
open-telemetry:mainfrom
rooks-ns:feat/context-scoped-attributes

Conversation

@rooks-ns

@rooks-ns rooks-ns commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Prototypes Context-scoped attributes (OTEP #4931, #8532), allowing Traces, Logs, and synchronous Metrics to automatically inherit attributes from the active Context without manually adding them at every call site.


Key Decisions (Spec Gaps Resolved)

  • Bound Instruments ignore Context Attributes:
    • Gap: OTEP Prepare release 1.20.0 #4931 omitted bound instruments because core specs removed bind(), but Java incubator retains it.
    • Resolution: Bound handles (BoundStorageHandle) record only to their pre-bound timeseries with zero runtime attribute merging. This guarantees zero allocations on hot paths and prevents cardinality explosion on shared infrastructure (e.g., connection pools). Unbound instruments continue to merge context attributes dynamically.
  • setNoParent() preserves Context Attributes:
    • Gap: setNoParent() historically reset context to Context.root(), unintentionally deleting context attributes and baggage.
    • Resolution: Replaced Context.root() with current.with(Span.getInvalid()). This detaches the parent span while keeping context attributes and baggage available for Sampler and SpanProcessor. Use setParent(Context.root()) for full isolation.

Signal Implementation Summary

  • API (api:incubator): ContextAttributes.add(Context, Attributes) (merges into context) and get(Context) (SDK reader).
  • Precedence: Attributes set directly on a span, log, or metric measurement always override context attributes on key collisions.
  • Trace (sdk:trace): Merges context attributes before Sampler.shouldSample(); unifies context across sampling, span creation, and SpanProcessor.onStart().
  • Logs (sdk:logs): Merges on emit() into an isolated record attributes map to prevent attribute leakage when reusing LogRecordBuilder.
  • Metrics (sdk:metrics): Merges context attributes on unbound instruments before view filtering; bypasses canUseRootContext optimization when active.
  • Config: Disabled by default. Enabled programmatically via Sdk*ProviderUtil or declaratively via context_scoped_attributes/development.

Verification

  • Trace: ContextScopedAttributesSpanTest (attributes, precedence, limits, and setNoParent() context preservation) & SdkSpanBuilderTest (legacy compatibility).
  • Metrics: ContextScopedAttributesMetricTest (dynamic unbound enrichment, bound handle isolation, same-context co-existence).
  • Logs: ContextScopedAttributesLogTest (merging, collision precedence, builder reuse).
  • Integration & Static Analysis: ContextScopedAttributesIntegrationTest (multi-signal E2E); clean runs across spotless, checkstyle, jApiCmp, and animalsniffer.

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-11-09 20:12 UTC

Two things need attention:

  • Required checks are failing — investigate the failures.
  • 17 review items — respond to each (e.g. link a commit, explain why not, ask a follow-up):
Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? Report it with what you expected; it helps us improve the dashboard.

@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.10471% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.35%. Comparing base (877820b) to head (a9bcfb9).
⚠️ Report is 26 commits behind head on main.

Files with missing lines Patch % Lines
...metry/sdk/logs/internal/SdkLoggerProviderUtil.java 75.00% 4 Missing ⚠️
...try/sdk/metrics/internal/SdkMeterProviderUtil.java 75.00% 4 Missing ⚠️
...nternal/state/DefaultSynchronousMetricStorage.java 87.09% 4 Missing ⚠️
...etry/sdk/trace/internal/SdkTracerProviderUtil.java 75.00% 4 Missing ⚠️
...rnal/state/CumulativeSynchronousMetricStorage.java 25.00% 3 Missing ⚠️
.../internal/state/DeltaSynchronousMetricStorage.java 25.00% 3 Missing ⚠️
...trics/internal/state/MeterProviderSharedState.java 40.00% 3 Missing ⚠️
...trics/internal/state/SynchronousMetricStorage.java 0.00% 3 Missing ⚠️
...in/java/io/opentelemetry/sdk/metrics/SdkMeter.java 71.42% 1 Missing and 1 partial ⚠️
...emetry/api/incubator/common/ContextAttributes.java 95.45% 0 Missing and 1 partial ⚠️
... and 7 more
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #8947      +/-   ##
============================================
- Coverage     90.41%   90.35%   -0.07%     
- Complexity    10981    11085     +104     
============================================
  Files          1019     1023       +4     
  Lines         30016    30306     +290     
  Branches       3917     3953      +36     
============================================
+ Hits          27140    27383     +243     
- Misses         1923     1956      +33     
- Partials        953      967      +14     

☔ 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.

@rooks-ns
rooks-ns force-pushed the feat/context-scoped-attributes branch from 403a57c to 7d8f465 Compare October 13, 2026 22:24
@rooks-ns
rooks-ns marked this pull request as ready for review October 14, 2026 18:25
@rooks-ns
rooks-ns requested a review from a team as a code owner October 14, 2026 18:25
@rooks-ns
rooks-ns force-pushed the feat/context-scoped-attributes branch 3 times, most recently from 3b2ce44 to e7872d7 Compare October 14, 2026 21:45
@rooks-ns
rooks-ns marked this pull request as draft October 18, 2026 12:22
@rooks-ns
rooks-ns force-pushed the feat/context-scoped-attributes branch from e7872d7 to 657d862 Compare October 19, 2026 23:48
@rooks-ns
rooks-ns marked this pull request as ready for review October 20, 2026 14:20
Implements prototype for Context-scoped attributes per OTEP open-telemetry#4931 and Issue open-telemetry#8532:
- Add ContextAttributes API in :api:incubator for setting/getting/extracting attributes on Context
- Support opt-in context-scoped attributes configuration across SdkTracerProvider, SdkLoggerProvider, and SdkMeterProvider
- Inherit context attributes in SdkSpanBuilder, SdkLogRecordBuilder, and synchronous metric storage (Delta & Cumulative)
- Expose configuration via declarative-config (tracer_provider, logger_provider, meter_provider)
- Add comprehensive unit, incubating, and multi-signal integration tests
@rooks-ns
rooks-ns force-pushed the feat/context-scoped-attributes branch from 657d862 to ef1468f Compare October 22, 2026 16:49
@rooks-ns

Copy link
Copy Markdown
Contributor Author

@jack-berg Please take a look . Thank you.

if (context == null) {
return Attributes.empty();
}
Attributes attributes = context.get(CONTEXT_ATTRIBUTES_KEY);

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.

I see usefulness in this feature but I find it hard to understand how to decide with instrumentations should include the attributes.

How to assert or qualify the carnality of the attribute?
Which instrumentations should skip an attribute?
Should we have a handler to decide which instrumentations opt to an attribute?
Are there attributes that make no sense across an entire context lifespan?

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.

Instrumentations are advised against including attributes (see javadoc at top).

Think about the core use cases: tenant id / user id. An application would add these to context early in request lifecycle via interceptor or filter. They would also configure the SDK to include the attributes as needed based on signal, scope, and and attribute key.

The OTEP left a lot of questions unanswered with the intent of discovering more via prototyping. If you have general feedback (i.e. not related to the java implementation), please add it to open-telemetry/opentelemetry-specification#5170 so all languages can benefit.

@jack-berg jack-berg left a comment

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.

Pretty good stuff - close to what I expected. Left a few comments but nothing should be too tricky.

private LoggerProviderModelAccessor() {}

static final String LOGGER_CONFIGURATOR = "logger_configurator/development";
static final String CONTEXT_SCOPED_ATTRIBUTES = "context_scoped_attributes/development";

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.

These are generated from the JSON schema defined in opentelemetry-configuration. We'll have to skip declarative config support until someone goes and defines the config surface area in the schema.

* Utility for associating {@link Attributes} with a {@link Context} for automatic propagation to
* telemetry signals produced within that context.
*/
public final class ContextAttributes {

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.

The otep was merged for this, but there has been no attempt yet to codify the otep in a spec PR. This repo is bound to content in the spec (otep != spec). I'm going to make a judgement call here and go ahead with this on the basis of the otep, but it can't live on indefinitely if nobody makes progress in the spec.

I want there to be a comment here reminding us of this.

Suggested change
public final class ContextAttributes {
// TODO (jack-berg): delete after 2027-07-01 if progress hasn't been made on https://github.com/open-telemetry/opentelemetry-java/issues/8532
public final class ContextAttributes {

* SdkLoggerProviderUtil#setContextScopedAttributesEnabled(SdkLoggerProviderBuilder, boolean)}.
*/
SdkLoggerProviderBuilder setContextScopedAttributesEnabled(
boolean contextScopedAttributesEnabled) {

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.

I know the otep talks about configuration as a boolean, but this is not going to be the final form. Its not expressive enough to handle cases where you have high and low cardinality attributes, and you want to include the low cardinality attribute on all signals and the high cardinality attributes only on spans and logs.

I'd like to start with a BiPredicate<InstrumentationScopeInfo, AttributeKey<?>>. This should give you expressiveness where you want / need it, without compromising convenience since you can just configure (unused, unused) -> true just as easily as true, and shouldn't impact performance.

if (!contextAttributes.isEmpty()) {
AttributesMap mergedAttributes =
recordAttributes != null
? AttributesMap.copyOf(recordAttributes)

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.

Copying the builder's attributes seems like a safety mechanism to prevent context attributes polluting the builder attributes in case of reuse. But builder is already not safe for reuse since attributes are passed to the SdkReadWriteLogRecord where a processor can mutate them and which allows a caller to further mutate the attributes after the fact.

So I think we should take steps to explicitly prevent builder reuse (separately from this PR) but don't need to spend the extra resources making a copy here.

@SuppressWarnings({"rawtypes", "ReferenceEquality"})
public Span startSpan() {
Context parentContext = parent == null ? Context.current() : parent;
if (noParent

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.

Should enabling attribute inheritance also change which unrelated context values samplers and processors receive for setNoParent()? Could we source inherited attributes from the ambient context without changing the existing parent-context behavior?

@opentelemetry-pr-dashboard

Copy link
Copy Markdown

Hi @rooks-ns — just a friendly reminder that this pull request is waiting on you. The dashboard status comment has the open items and is kept current.

  • Replying is enough to hand it off — answer, explain why no change is needed, or ask a follow-up. The dashboard routes it onward once nothing on the list is waiting on you.
  • To hand it back for any other reason, including the dashboard getting this wrong, comment /dashboard route:reviewers.

…oped attributes prototype (open-telemetry#8532)

- Trace: Isolate parent context and ambient Baggage when setNoParent() is configured by passing Context.root() to samplers/processors while resolving context attributes from current context.
- Collisions: Add amortized O(1) AttributesMap.contains(AttributeKey<?>) to enforce name-based attribute identity and ensure explicit attributes take precedence over context attributes regardless of type.
- Logs: Eliminate AttributesMap.copyOf defensive allocation by mutating builder attributes in-place during emit().
- Filtering: Upgrade trace, log, and metric providers from a boolean flag to BiPredicate<InstrumentationScopeInfo, AttributeKey<?>> contextScopedAttributesFilter; curry predicate in SdkMeter for synchronous metric storage; preserve context-oblivious bound instruments.
- API: Add ContextAttributes.set() for replacement and clearing, retain add() for additive merging, enforce strict requireNonNull validation, and record maintainer deprecation TODO.
- Config: Revert declarative-config prototype additions to match upstream main.
@rooks-ns
rooks-ns force-pushed the feat/context-scoped-attributes branch from 12e5e76 to a9bcfb9 Compare November 6, 2026 18:18
import javax.annotation.Nullable;

// TODO (jack-berg): delete after 2027-07-01 if progress hasn't been made on
// https://github.com/open-telemetry/opentelemetry-java/issues/8532

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.

Suggested change
// https://github.com/open-telemetry/opentelemetry-java/issues/8532
// https://github.com/open-telemetry/opentelemetry-specification/pull/4931

Its progress on the spec we want to gate on

* Utility for associating {@link Attributes} with a {@link Context} for automatic propagation to
* telemetry signals produced within that context.
*
* <h2>Usage and Authoring Guidance</h2>

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.

Less is more here.

General library instrumentations should avoid populating context-scoped attributes to prevent injecting library-internal concerns into ambient application telemetry.

If this isn't a direct quote from the otep, omit it.

Context-scoped attributes are stamped across all active telemetry signals produced within the context.

Not by default. Should more simply indicate that SDKs can configure context scoped attributes to be stamped across telemetry signals, with the ability to configure based on signal, scope, and attribute key. The metric cardinality warning is good to keep.

*/
public static Context add(Context context, Attributes attributes) {
requireNonNull(context, "context");
requireNonNull(attributes, "attributes");

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.

Should log and gracefully degrade if these are null, following advice in "docs/knowledge/api-design.md -> Null guards -> Runtime / instrumentation-time boundaries (Span methods, metric recordings, log builders)"

if (context == null) {
return Attributes.empty();
}
Attributes attributes = context.get(CONTEXT_ATTRIBUTES_KEY);

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.

Instrumentations are advised against including attributes (see javadoc at top).

Think about the core use cases: tenant id / user id. An application would add these to context early in request lifecycle via interceptor or filter. They would also configure the SDK to include the attributes as needed based on signal, scope, and and attribute key.

The OTEP left a lot of questions unanswered with the intent of discovering more via prototyping. If you have general feedback (i.e. not related to the java implementation), please add it to open-telemetry/opentelemetry-specification#5170 so all languages can benefit.

*
* @param key the attribute key whose name to look up. Must not be null.
*/
public boolean contains(AttributeKey<?> key) {

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 its checking containment regardless of type, let's have the method signature reflect this by accepting String. The current signature suggests this method is redundant to calling get(AttributeKey) != null.

* the series is resolved at bind time. Backs bound instruments.
* the series is resolved at bind time. Backs bound instruments. Bound handles record strictly
* against their bound attributes and do not inherit dynamic context-scoped attributes, preserving
* bare-metal performance and isolating infrastructure metrics from runtime cardinality explosion.

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.

Bare metal isn't quite right. Also, "isolating infrastructure metrics" suggests that bound instruments are somehow related to infrastructure metrics, but bound instruments are a good choice whenever you know the label set up front.

I think better to just say that bound handles do not inherit dynamic context-scoped attributes, and maybe add a TODO to review this.

this.memoryMode = memoryMode;
}

CumulativeSynchronousMetricStorage(

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.

No new overloads for internal classes like this. Just update the existing constructor to include the new param and update all the tests which reference it accordingly. Same applies elsewhere.

@SuppressWarnings({"rawtypes", "unchecked"})
private Attributes getMergedContextAttributes(Attributes attributes, Context context) {
if (contextScopedAttributesFilter == null) {
return null;

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.

Why return null instead of just returning the attributes param unchanged?

return null;
}
boolean[] hasMatched = new boolean[1];
AttributesBuilder builder = Attributes.builder();

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.

Can we avoid initializing the builder until we know we have a matched attribute?

if (contextScopedAttributesFilter == null) {
return null;
}
Attributes contextAttributes = StateIncubatingUtil.getContextAttributes(context);

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.

I don't see a guard that ensures we don't call incubating utils unless we know that incuabtor is on the classpath. I think the test is passing because the check on line 164 essentially gates the same thing. But what if someone configures a contextScopedAttributesFilter without the api-incubator present?

This branch has not been deployed

No deployments
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.

3 participants