From 8056dbe45e4923a448e6f856ddfbf065d44b71a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 13 Jun 2022 17:56:55 +0200 Subject: [PATCH 01/11] Propose Context-scoped attributes. --- text/0000-context-scoped-attributes.md | 248 ++++++++++++++++++ .../0000-context-scoped-attributes.drawio.png | Bin 0 -> 34980 bytes 2 files changed, 248 insertions(+) create mode 100644 text/0000-context-scoped-attributes.md create mode 100644 text/img/0000-context-scoped-attributes.drawio.png diff --git a/text/0000-context-scoped-attributes.md b/text/0000-context-scoped-attributes.md new file mode 100644 index 000000000..c66e7fdfd --- /dev/null +++ b/text/0000-context-scoped-attributes.md @@ -0,0 +1,248 @@ +# Context-scoped attributes + +Add Context-scoped telemetry attributes which typically apply to all signals associated with a trace as it crosses a single service. + +## Motivation + +Why should we make this change? What new value would it bring? What use cases does it enable? + +This OTEP aims to address various related demands that have been brought up in the past, where the scope of resource attributes is too broad, but the scope of span attributes is too narrow. For example, this happens where there is a mismatch between the OpenTelemetry SDK’s (and thus TracerProvider’s, MeterProvider’s) process-wide initialization and the semantic scope of a (sub)service. + +A concrete example is posed in open issue [open-telemetry/opentelemetry-specification#335](https://github.com/open-telemetry/opentelemetry-specification/issues/335) “Consider adding a resource (semantic convention) to distinguish HTTP applications (http.app attribute)”. +If the opentelemetry-java agent is injected in a JVM running a classical Java Application Server (such as tomcat), there will be a single resource for the whole JVM. +The application server can host multiple independent applications. +There is currently no way to distinguish between these, other than with the generic HTTP attributes like http.route, http.target. +The issue proposes to add an http.app attribute but it is unclear where this could be placed. +The resource cannot be used, since there is only one that is shared between multiple applications. +A span attribute on the root span could be used. +However, logically, the app attribute would apply to all spans within the trace as it crosses the app server, as shown in the diagram below: + +[Diagram showing how two traces cross a single service, having a http.app attribute applied to all spans within that service of each trace](!img/../0000-context-scoped-attributes.md) + +This example shows two traces, with two "HTTP GET" root spans, one originating from service `frontend` and another from `user-verification-batchjob`. +Each of these HTTP GET spans calls into a third service `my-teams-tomcat` which is a Java Tomcat application server. +That service hosts two distinct HTTP applications, and as each of the traces crosses it, all the spans have a +respective `http.app` associated with it. When the `my-teams-tomcat` service makes a call to another service `authservice`, +that attribute does *not* apply to the remote child spans. + +Using only currently specified mechanisms, the http.app attribute could be set, e.g., on the `/myapp/users/{userid}` root span, but it logically applies also to the `HTTP GET` child span which is executed in the same app. + +A similar problem occurs with `faas.id` and `faas.name` ([Function as a Service resource semantic conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/faas.md)) on Azure Functions. +While both are defined as resource attributes, an Azure function app hosts multiple co-deployed functions with different names and IDs. +In PR [open-telemetry/opentelemetry-specification#2502](https://github.com/open-telemetry/opentelemetry-specification/pull/2502), this was solved by allowing to set these resource attributes on the FaaS root span instead. +This has the drawback that the context of which function a span is executed in is lost in any child spans that may occur within the function (app). + +There is also issue [open-telemetry/opentelemetry-specification#1089](https://github.com/open-telemetry/opentelemetry-specification/issues/1089) “Add BeforeEnd to have a callback where the span is still writeable” which seems to be motivated by essentially the same problem, though the motivation is stated more on a technical level: “What I tried to implement is a feature called trace field in Honeycomb (reference: [AddFieldToTrace](https://godoc.org/github.com/honeycombio/beeline-go#AddFieldToTrace))”, though lately the issue seems to go into a slightly different direction. + +This motivation analogously also applies to other signals (metrics, logs) which will have very similar problems. E.g. the http.app attribute also looks like something you may want to split metrics by or use as a query for logs (“I want to see all logs produced by the `http.app=myapp`”). + +## Explanation + +The context-scoped attributes allows you to attach attributes to all telemetry signals emitted within a Context (or, following the usual rules of Context values, any child context thereof unless overridden). Context-scoped attributes are normal attributes, which means you can use strings, integers, floating point numbers, booleans or arrays thereof, just like for span or resource attributes. Context-scoped attributes are associated with all telemetry signals emitted while the Context containing the Context-scoped attributes is active and are available to telemetry exporters. For spans, the context within which the span is started applies. Like other telemetry APIs, Context-scoped attributes are write-only for applications. You cannot query the currently set Context-scoped attributes, they are only available on the SDK level (e.g. to telemetry exporters, Samplers and SpanProcessors). + +Context-scoped attributes should be thought of equivalent to adding the attribute directly to each single telemetry item it applies to. + +### Comparing Context-scoped attributes to Baggage + +Context-scoped attributes and [baggage](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/baggage/api.md) have two major commonalities: + +* Both are "bags of attributes" +* Both are propagated identically in-process (Baggage also lives on the Context). + +However, the use cases are very different: Baggage is meant to transport app-level data along the path of execution, while execution-scoped attributes are annotations for all telemetry in the program execution "below" this point. + +This also explains the functional & API differences: + +* Most important point: Baggage is meant to be propagated; in fact, this is the main use case of baggage. While a propagator for Context-scoped attributes would be implementable, it would break the intended meaning by extending the scope of attributes to called services and would also raise many security concerns. +* Baggage is both readable and writable for the application, while execution-scoped attributes, like all other telemetry attributes, are write-only. +* Baggage only supports string values. +* Baggage is not available to telemetry exporters (although e.g., a SpanProcessor could be used to change that), while that's the whole purpose of execution-scoped attributes. + +### Comparing Context-scoped attributes to Scope Attributes + +Context-scoped attributes and the recently added [Scope attributes](https://github.com/open-telemetry/oteps/pull/201) have these commonalities: + +* Both have "scope" in the name +* Both apply a set of telemetry attributes to to a set of telemetry items (those in their "scope") + +What is different is the scope. While Scope attributes apply to all items emitted by the same Tracer, Meter or LogEmitter (i.e., typically the same telemetry-implementing unit of code), +the scope of Context-scoped attributes is determined at runtime by the Context. + +In practice, these scopes will [often be completely orthogonal](#scope-and-context), i.e., as the Context is "propagated" in-process through through a single +service, there will often be exactly one span per Tracer (e.g., one span from the HTTP server instrumentation as the request arrives, +and another one from a HTTP client instrumentation, as a downstream service is called) + +I suggest calling Scope Attributes emitter scope attributes for better distinguishability. + +## Internal details + +This section explains the API-level, SDK-level and exporter-level changes that are expected to implement this. + +### API changes + +The API would be extended with a variation of the following: + +* `Context AddContextScopedAttributes(Context context, Attributes attributes)` + +`AddContextScopedAttributes` takes a set of attributes and a Context and returns a new Context that has the given context attributes set in addition to any already set ones. +If the context already contains any attributes with that name, they MUST be overwritten with the attributes of the later call. +If an associated telemetry item already has an attribute with a name that is also in the Context-scoped attributes, the telemetry attribute MUST take precedence. +Since the typical use case for this function is expected to be just before a local root span for the trace is created, no particular care needs to be taken to optimize merging of attributes from calls to this function on a Context that already has Context-scoped attributes. +As for all APIs, care should be taken to expose it in a way that API-users do not bind themselves to a particular SDK implementation, such as the OpenTelemetry default SDK (for example, by going through a `GlobalOpenTelemetry.GetInstance()` or `GlobalOpenTelemetry.GetInstance().GetContextAttributeWriter()` singleton that returns an interface implementation). +As this is a write-only API, the API-only implementation should do nothing at all. + +### SDK changes + +The SDK-implementation could look like this: + +```C# +Context AddContextScopedAttributes(Context context, Attributes attributes) { + return context.WithValue( + _CTX_KEY_SCOPED_ATTRS, + MergeAttributes(context.GetValue(_CTX_KEY_SCOPED_ATTRS), attributes)); +} +``` + +The Context-scoped attributes must be available for all telemetry items they are logically associated with. +For example, the `ReadableSpan` should include the context attributes in the list of span attributes, and they must be included into the list of attributes that the sampler receives. +This will be an implementation-level change without any changes in the API-surface of the SDK (i.e., it is not necessary to make context-scoped attributes distinguishable from “direct” telemetry attributes). + +### Exporter changes (none required) + +Exporters should set the attributes on all telemetry items they belong to as if they were set directly on them. +For example, a span with span attribute `foo=bar` and Context attribute `x=y` would be exported with span attributes `foo=bar, x=y`. +This should happen by default as the telemetry items will include the context-scoped attributes among their “direct” attributes. + +## Trade-offs and mitigations + +This OTEP suggests making context-scoped attributes indistinguishable from attributes set directly on the telemetry items. +This should ease implementation on both backends and OpenTelemetry client libraries, and hopefully also reduces conceptual complexity, but it does makes the feature less powerful. +No use case is known to the proposer of the OTEP that would require this power though. +Nevertheless, some implications of making the Context-scoped attributes distinguishable are explored in [Prior art and alternatives](#prior-art-and-alternatives). + +This OTEP relies on the [`Context`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/README.md) concept that has been in the spec since 1.0. +However, at least .NET went 1.0 before the spec and does not include a conforming Context concept. +For .NET, an async-local variable that holds the Context-scoped attributes seems to be the most useful implementation of this OTEP (similar to how Baggage is implemented there). + +## Prior art and alternatives + +### Alternative: Making Context-scoped attributes semantically distinct from other telemetry attributes + +As explained in trace-offs and mitigations, this alternative was not selected for the main text, because it seems to add more effort and complexity than value. + +#### SDK-changes for semantically distinct Context-scoped attributes + +The context-scoped attributes would not be included with the other telemetry attributes. +Instead, there would be separate getters for the context-scoped attributes on all telemetry items. + +Additionally, at the SDK-level, the following could be provided, mainly for access from samplers: + +* `Attributes GetContextScopedAttributes(Context context)` + +Alternatively, a new argument could be added to the sampler input (languages that implement the sampler arguments with a single object with multiple properties can implement this in a backwards-compatible way). + +While in theory, applications and instrumentations could take a dependency on the SDK, and call this function from anywhere, this is strongly discouraged and not guaranteed to work. +Languages may even opt for an unorthodox way to expose this that only works from within SDK callbacks (e.g. by having a separate type SdkContext / ReadableContext passed to them). + +#### Exporter changes for semantically distinct Context-scoped attributes + +Exporters would need to explicitly handle these attributes. As most protocols won’t have support for this concept, the output should probably end up the same as with the indistinguishable attributes. + +#### OTLP protocol changes for semantically distinct Context-scoped attributes (optional) + +With the implementation suggested in the main section of the OTEP, there is no need to change the OTLP protocol for this feature, as the Context attributes should semantically be equivalent to adding the attribute to every associated telemetry item. + +If context attributes were distinguishable, the OTLP protocol could be extended with special support for context-scoped attributes. The scope of Context-scoped attributes is orthogonal to the existing hierarchy of Resource -> (Emitter)Scope -> item. Thus, each single telemetry item needs to get a new field with its context-scoped attributes. This could be just a repeated opentelemetry.proto.common.v1.KeyValue contextAttributes + +If a more deduplicated representation is desired, it could alternatively be an index into a list of Context-scoped attributes that is sent once in the root message to be shared among multiple telemetry items. + +```protobuf +message TracesData { + // ... + repeated opentelemetry.proto.common.v1.KeyValueList contexts = 12345; +} +``` + +As mentioned in the API-changes, it is expected that no particular care has to be taken to optimize for merging attributes, so a repeated KeyValueList seems to fit the bill. +If a need for such optimization is found, a representation of a tree-structure of these Contexts might be needed, e.g., by storing a dedicated submessage that also contains a parent index. +This could become complex to implement in the exporter though, and generic compression (e.g., gzip) may be more effective. + + + +Yet another alternative, that would also have implications for semantics, would be to merge Context-scoped attributes with the Resource of all associated telemetry items, or with the emitter scope (formerly instrumentation library info, currently only named “Scope”), instead of their own attributes. +This would mean that every distinct set of Context-scoped attributes gets sent within a distinct ResourceSpans/ScopeSpans message, duplicating the resource and (emitter-)scope attributes that many times (a cross-product, if you will). +This approach seems semantically interesting, but could lead to larger export messages, especially if we assume that very often there will only be one span of the same emitter-scope per Context-scope (e.g., you typically will only have one single Span produced by the HTTP server instrumentation as the trace crosses a single service). + +#### Alternative: Associating Context-scoped attributes with the span on end and setting attributes on parent Contexts + +This alternative seems to be a valid option which is just as easy to implement as the approach in the main context, but it feels a bit less natural. + +For spec-conforming implementations of tracing (which excludes .NET in this case), ending/starting a span is independent from setting it as active in a Context. +This allows ending a span in a different context from starting it in. +One use case this could enable is pushing attributes up to a parent span. +For example, an incoming web request might have a low-level HTTP span as root, which might then be dispatched to a HTTP handler method that logically is a Function as a Service span. +In this case, one might want to associate the HTTP parent span with the FaaS function (faas.id, faas.name, …) as well. +This could be implemented by leaving the context with these context-scoped attributes active after returning from the handler method. + +This approach would be more similar to what is suggested in [open-telemetry/opentelemetry-specification#1089](https://github.com/open-telemetry/opentelemetry-specification/issues/1089). +However, the approach used in the motivation for that issue seems to go even further and makes pushing attributes up to the local root span the default. +See the [next alternative](#alternative-with-prior-art-bubbling-attributes-up-to-the-root-span). + +### Alternative with prior art: Bubbling attributes up to the root span + +This alternative is explored because it came up in a spec issue before, but it seems to be quite complex and not clearly any more useful than the semantics in the main OTEP. + +[open-telemetry/opentelemetry-specification#1089](https://github.com/open-telemetry/opentelemetry-specification/issues/1089) implements “trace-fields” (trace attributes) and seems to be based on a shared mutable set of Attributes created together with the root span instead of having an immutable set per (child) Context. If we wanted these semantics, that would probably warrant a modified API replacing AddContextScopedAttributes: + +* `Context CreateContextScopedRoot(Context)` +* `void SetAttributes(Context context, Attributes newAttributes)` + +CreateContextScopedRoot would return a new context with an empty set of mutable attributes. +Child contexts would inherit a reference to the same mutable set. Additionally, it would seem appropriate that setting a span as active in a context that has neither an active span, nor a mutable attribute set, would implicitly also CreateContextScopedRoot. + +These semantics seem to be more complex and prone to surprises though. +A typical “failure mode” when trying to associate attributes with the whole trace (or rather whole local part of the trace within a service) including parent spans would be when some in-process parent span has already ended (e.g. asynchronous fire-and-forget invocation), or if the parent span has created another child span that already ended at the time SetAttributes is called. + +One also needs to define what happens when attributes are added after a span has ended (or a metric was recorded) but before it is exported. +Probably you would want to have a snapshot of the mutable attributes at time of emitting the telemetry item (ending the span). + +Another difficult question would be what to do if SetAttributes is called with a context that does not have mutable attributes yet. +Should this case be prevented by having every child of the root context eagerly create mutable attributes? Should mutable attributes be created in-place, modifying the Context (deliberately made impossible with the current context API, where setting a key always returns a new Context). +Should it always return (new) Context then? +Should it be a no-op? + +Do note that with the `CreateContextScopedRoot` API, it would be possible to use this approach even independently of the tracing signal, just as the main OTEP. + +### Alternative: Associating attributes through indirectly through spans + +This does not seem particularly useful, but the existence of this alternative should serve as a reminder that the trace structure might be different from the (child) context structure. + +If going for the [“Making context-scoped attributes semantically distinct from other telemetry attributes” alternative](#alternative-making-context-scoped-attributes-semantically-distinct-from-other-telemetry-attributes), a possibility would pop up to store context-scoped attributes not on the context, but on spans (in a dedicated property). +Other telemetry signals could still go through the active span to associate subtrace-scoped attributes. +The (subtle?) difference would be that these attributes follow the trace tree instead of the context tree. + +#### Alternative: An implementation for traces on the Backend + +A receiver of trace data (and only trace data!) has information about the execution flow through the parent-child relationship of spans. +It could use that to propagate/copy attributes from the parent span to children after receiving them. +However, there are several problems/differences in what is possible: + +* The backend needs to determine which attributes only apply to a particular span, and which apply to a whole subtrace. + In absence of a dedicated API that allows instrumentation libraries to add this information, it can only use pre-determined rules (e.g. fixed list of attribute keys) for that. +* Spans are only sent (by usual SpanProcessor+Exporter pipelines) when they are ended, meaning that the parent will usually arrive after the children. + This complicates processing, and can make additional buffering and/or reprocessing (e.g. repeated on-read processing) necessary. +* To only propagate the attributes within a service, [open-telemetry/oteps#182](https://github.com/open-telemetry/oteps/pull/182) (sending the `IsRemote` property of the (parent) span Context in the OTLP protocol) would be required. + This would also require using OTLP, or another protocol (is there any?) that transports this information. + Also, the OTLP exporters & language SDKs used in the participating services need to actually implement the feature. + In absence of that, the span kind (server/consumer as child of client/producer) would be the only heuristic to try working around this. + +Additionally, Context-scoped attributes would have the advantage of being usable for metrics and logs as well. +The backend could alternatively try to re-integrate attributes from matched spans, if it's a single backend for all signals, but this would probably become even more costly to implement. + +## Open questions + +None known at the moment (but see [Trade-offs and mitigations](#trade-offs-and-mitigations) and [Prior art and alternatives](#prior-art-and-alternatives)). + +## Future possibilities + +With the changes implemented in this OTEP, we will hopefully be unblock some long-standing specification issues. See [Motivation](#motivation). diff --git a/text/img/0000-context-scoped-attributes.drawio.png b/text/img/0000-context-scoped-attributes.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..99d16155432a971d84cb9d9fc74331851c15cb50 GIT binary patch literal 34980 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 l{#yy{$#rBd9X= literal 0 HcmV?d00001 From 0527883fa2ea26ee7732676c566fdae6f2d55596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 13 Jun 2022 18:07:53 +0200 Subject: [PATCH 02/11] Fill in PR#, remove leftover placeholder. --- ...ributes.md => 0207-context-scoped-attributes.md} | 4 +--- ...ng => 0207-context-scoped-attributes.drawio.png} | Bin 2 files changed, 1 insertion(+), 3 deletions(-) rename text/{0000-context-scoped-attributes.md => 0207-context-scoped-attributes.md} (99%) rename text/img/{0000-context-scoped-attributes.drawio.png => 0207-context-scoped-attributes.drawio.png} (100%) diff --git a/text/0000-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md similarity index 99% rename from text/0000-context-scoped-attributes.md rename to text/0207-context-scoped-attributes.md index c66e7fdfd..86c2e8d9e 100644 --- a/text/0000-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -4,8 +4,6 @@ Add Context-scoped telemetry attributes which typically apply to all signals ass ## Motivation -Why should we make this change? What new value would it bring? What use cases does it enable? - This OTEP aims to address various related demands that have been brought up in the past, where the scope of resource attributes is too broad, but the scope of span attributes is too narrow. For example, this happens where there is a mismatch between the OpenTelemetry SDK’s (and thus TracerProvider’s, MeterProvider’s) process-wide initialization and the semantic scope of a (sub)service. A concrete example is posed in open issue [open-telemetry/opentelemetry-specification#335](https://github.com/open-telemetry/opentelemetry-specification/issues/335) “Consider adding a resource (semantic convention) to distinguish HTTP applications (http.app attribute)”. @@ -17,7 +15,7 @@ The resource cannot be used, since there is only one that is shared between mult A span attribute on the root span could be used. However, logically, the app attribute would apply to all spans within the trace as it crosses the app server, as shown in the diagram below: -[Diagram showing how two traces cross a single service, having a http.app attribute applied to all spans within that service of each trace](!img/../0000-context-scoped-attributes.md) +[Diagram showing how two traces cross a single service, having a http.app attribute applied to all spans within that service of each trace](!img/../0207-context-scoped-attributes.md) This example shows two traces, with two "HTTP GET" root spans, one originating from service `frontend` and another from `user-verification-batchjob`. Each of these HTTP GET spans calls into a third service `my-teams-tomcat` which is a Java Tomcat application server. diff --git a/text/img/0000-context-scoped-attributes.drawio.png b/text/img/0207-context-scoped-attributes.drawio.png similarity index 100% rename from text/img/0000-context-scoped-attributes.drawio.png rename to text/img/0207-context-scoped-attributes.drawio.png From 4e040bb25d3cdb2ebd63fcda6138cebb5b3862d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 13 Jun 2022 18:13:18 +0200 Subject: [PATCH 03/11] Fix img inclusion. --- text/0207-context-scoped-attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index 86c2e8d9e..be186b178 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -15,7 +15,7 @@ The resource cannot be used, since there is only one that is shared between mult A span attribute on the root span could be used. However, logically, the app attribute would apply to all spans within the trace as it crosses the app server, as shown in the diagram below: -[Diagram showing how two traces cross a single service, having a http.app attribute applied to all spans within that service of each trace](!img/../0207-context-scoped-attributes.md) +![Diagram showing how two traces cross a single service, having a http.app attribute applied to all spans within that service of each trace](./img/0207-context-scoped-attributes.drawio.png) This example shows two traces, with two "HTTP GET" root spans, one originating from service `frontend` and another from `user-verification-batchjob`. Each of these HTTP GET spans calls into a third service `my-teams-tomcat` which is a Java Tomcat application server. From 1950718561e337a8c986fa9fe98f507fa1467160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 13 Jun 2022 18:22:03 +0200 Subject: [PATCH 04/11] Typo. --- text/0207-context-scoped-attributes.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index be186b178..e15516d45 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -1,7 +1,6 @@ # Context-scoped attributes Add Context-scoped telemetry attributes which typically apply to all signals associated with a trace as it crosses a single service. - ## Motivation This OTEP aims to address various related demands that have been brought up in the past, where the scope of resource attributes is too broad, but the scope of span attributes is too narrow. For example, this happens where there is a mismatch between the OpenTelemetry SDK’s (and thus TracerProvider’s, MeterProvider’s) process-wide initialization and the semantic scope of a (sub)service. @@ -243,4 +242,4 @@ None known at the moment (but see [Trade-offs and mitigations](#trade-offs-and-m ## Future possibilities -With the changes implemented in this OTEP, we will hopefully be unblock some long-standing specification issues. See [Motivation](#motivation). +With the changes implemented in this OTEP, we will hopefully unblock some long-standing specification issues. See [Motivation](#motivation). From 94eec65c401d5e6fb6ed09db815d17c868ae5539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 14 Jun 2022 09:07:58 +0200 Subject: [PATCH 05/11] Fix doubled words. --- text/0207-context-scoped-attributes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index e15516d45..b624e3378 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -60,12 +60,12 @@ This also explains the functional & API differences: Context-scoped attributes and the recently added [Scope attributes](https://github.com/open-telemetry/oteps/pull/201) have these commonalities: * Both have "scope" in the name -* Both apply a set of telemetry attributes to to a set of telemetry items (those in their "scope") +* Both apply a set of telemetry attributes to a set of telemetry items (those in their "scope") What is different is the scope. While Scope attributes apply to all items emitted by the same Tracer, Meter or LogEmitter (i.e., typically the same telemetry-implementing unit of code), the scope of Context-scoped attributes is determined at runtime by the Context. -In practice, these scopes will [often be completely orthogonal](#scope-and-context), i.e., as the Context is "propagated" in-process through through a single +In practice, these scopes will [often be completely orthogonal](#scope-and-context), i.e., as the Context is "propagated" in-process through a single service, there will often be exactly one span per Tracer (e.g., one span from the HTTP server instrumentation as the request arrives, and another one from a HTTP client instrumentation, as a downstream service is called) From ca79eaf1525fac11f6ee788b89119fea5a5f2c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Wed, 15 Jun 2022 11:24:33 +0200 Subject: [PATCH 06/11] Fix heading hierarchy in alternatives. --- text/0207-context-scoped-attributes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index b624e3378..58469965d 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -170,7 +170,7 @@ Yet another alternative, that would also have implications for semantics, would This would mean that every distinct set of Context-scoped attributes gets sent within a distinct ResourceSpans/ScopeSpans message, duplicating the resource and (emitter-)scope attributes that many times (a cross-product, if you will). This approach seems semantically interesting, but could lead to larger export messages, especially if we assume that very often there will only be one span of the same emitter-scope per Context-scope (e.g., you typically will only have one single Span produced by the HTTP server instrumentation as the trace crosses a single service). -#### Alternative: Associating Context-scoped attributes with the span on end and setting attributes on parent Contexts +### Alternative: Associating Context-scoped attributes with the span on end and setting attributes on parent Contexts This alternative seems to be a valid option which is just as easy to implement as the approach in the main context, but it feels a bit less natural. @@ -218,7 +218,7 @@ If going for the [“Making context-scoped attributes semantically distinct from Other telemetry signals could still go through the active span to associate subtrace-scoped attributes. The (subtle?) difference would be that these attributes follow the trace tree instead of the context tree. -#### Alternative: An implementation for traces on the Backend +### Alternative: An implementation for traces on the Backend A receiver of trace data (and only trace data!) has information about the execution flow through the parent-child relationship of spans. It could use that to propagate/copy attributes from the parent span to children after receiving them. From a97a0f68c311951ee0b7370ac8f5eb9bf254fbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Wed, 15 Jun 2022 11:27:06 +0200 Subject: [PATCH 07/11] Typo --- text/0207-context-scoped-attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index 58469965d..714e644fa 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -210,7 +210,7 @@ Should it be a no-op? Do note that with the `CreateContextScopedRoot` API, it would be possible to use this approach even independently of the tracing signal, just as the main OTEP. -### Alternative: Associating attributes through indirectly through spans +### Alternative: Associating attributes indirectly through spans This does not seem particularly useful, but the existence of this alternative should serve as a reminder that the trace structure might be different from the (child) context structure. From 128ae59cad968b6c3676f98a86854d6accdc4238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Wed, 22 Jun 2022 17:34:28 +0200 Subject: [PATCH 08/11] Kick CI. From 3193890792464265ad7d9bd5331a1415ed1a9dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Thu, 11 Aug 2022 18:13:43 +0200 Subject: [PATCH 09/11] Address PR feedback (wording fixes & improvements) --- text/0207-context-scoped-attributes.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index 714e644fa..846d16c5a 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -46,31 +46,32 @@ Context-scoped attributes and [baggage](https://github.com/open-telemetry/opente * Both are "bags of attributes" * Both are propagated identically in-process (Baggage also lives on the Context). -However, the use cases are very different: Baggage is meant to transport app-level data along the path of execution, while execution-scoped attributes are annotations for all telemetry in the program execution "below" this point. +However, the use cases are very different: Baggage is meant to transport app-level data along the path of execution, while Context-scoped attributes are annotations for telemetry in the same execution path. This also explains the functional & API differences: * Most important point: Baggage is meant to be propagated; in fact, this is the main use case of baggage. While a propagator for Context-scoped attributes would be implementable, it would break the intended meaning by extending the scope of attributes to called services and would also raise many security concerns. -* Baggage is both readable and writable for the application, while execution-scoped attributes, like all other telemetry attributes, are write-only. +* Baggage is both readable and writable for the application, while Context-scoped attributes, like all other telemetry attributes, are write-only. * Baggage only supports string values. -* Baggage is not available to telemetry exporters (although e.g., a SpanProcessor could be used to change that), while that's the whole purpose of execution-scoped attributes. +* Baggage is not available to telemetry exporters (although e.g., a SpanProcessor could be used to change that), while that's the whole purpose of Context-scoped attributes. -### Comparing Context-scoped attributes to Scope Attributes +### Comparing Context-scoped attributes to Instrumentation Scope Attributes -Context-scoped attributes and the recently added [Scope attributes](https://github.com/open-telemetry/oteps/pull/201) have these commonalities: +Context-scoped attributes and the recently added [Instrumentation Scope attributes](https://github.com/open-telemetry/oteps/pull/201) have these commonalities: * Both have "scope" in the name * Both apply a set of telemetry attributes to a set of telemetry items (those in their "scope") -What is different is the scope. While Scope attributes apply to all items emitted by the same Tracer, Meter or LogEmitter (i.e., typically the same telemetry-implementing unit of code), +While Instrumentation Scope attributes apply to all items emitted by the same Tracer, Meter or LogEmitter (i.e., typically the same telemetry-implementing unit of code), the scope of Context-scoped attributes is determined at runtime by the Context. +This also means that the values for instrumentation scope are usually defined at build time, +while context-scoped attributes will most often have values determined at runtime. -In practice, these scopes will [often be completely orthogonal](#scope-and-context), i.e., as the Context is "propagated" in-process through a single +In practice, Instrumentation scope and Context scope will [often be completely orthogonal](#scope-and-context): +When the Context is flows in-process through a single service, there will often be exactly one span per Tracer (e.g., one span from the HTTP server instrumentation as the request arrives, and another one from a HTTP client instrumentation, as a downstream service is called) -I suggest calling Scope Attributes emitter scope attributes for better distinguishability. - ## Internal details This section explains the API-level, SDK-level and exporter-level changes that are expected to implement this. @@ -166,9 +167,9 @@ This could become complex to implement in the exporter though, and generic compr -Yet another alternative, that would also have implications for semantics, would be to merge Context-scoped attributes with the Resource of all associated telemetry items, or with the emitter scope (formerly instrumentation library info, currently only named “Scope”), instead of their own attributes. -This would mean that every distinct set of Context-scoped attributes gets sent within a distinct ResourceSpans/ScopeSpans message, duplicating the resource and (emitter-)scope attributes that many times (a cross-product, if you will). -This approach seems semantically interesting, but could lead to larger export messages, especially if we assume that very often there will only be one span of the same emitter-scope per Context-scope (e.g., you typically will only have one single Span produced by the HTTP server instrumentation as the trace crosses a single service). +Yet another alternative, that would also have implications for semantics, would be to merge Context-scoped attributes with the Resource of all associated telemetry items, or with the instrumentaiton scope (formerly instrumentation library info), instead of their own attributes. +This would mean that every distinct set of Context-scoped attributes gets sent within a distinct ResourceSpans/ScopeSpans message, duplicating the resource and instrumentation scope attributes that many times (a cross-product, if you will). +This approach seems semantically interesting, but could lead to larger export messages, especially if we assume that very often there will only be one span of the same instrumentation-scope per Context-scope (e.g., you typically will only have one single Span produced by the HTTP server instrumentation as the trace crosses a single service). ### Alternative: Associating Context-scoped attributes with the span on end and setting attributes on parent Contexts From 94d344dd712ee3bfd56eef055079af56c2fa8085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Thu, 11 Aug 2022 18:31:57 +0200 Subject: [PATCH 10/11] Add baggage-based implementation as alternative. --- text/0207-context-scoped-attributes.md | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index 846d16c5a..c61b90f7d 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -39,6 +39,8 @@ The context-scoped attributes allows you to attach attributes to all telemetry s Context-scoped attributes should be thought of equivalent to adding the attribute directly to each single telemetry item it applies to. + + ### Comparing Context-scoped attributes to Baggage Context-scoped attributes and [baggage](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/baggage/api.md) have two major commonalities: @@ -237,6 +239,33 @@ However, there are several problems/differences in what is possible: Additionally, Context-scoped attributes would have the advantage of being usable for metrics and logs as well. The backend could alternatively try to re-integrate attributes from matched spans, if it's a single backend for all signals, but this would probably become even more costly to implement. +### Alternative: Implementing Context-scoped attributes on top of Baggage + +See also: + +* [Related GitHub discussion](https://github.com/open-telemetry/oteps/pull/207#pullrequestreview-1055913542) +* Section [Comparing Context-scoped attributes to Baggage](#comp-baggage) above + +Instead of using a dedicated Context key and APIs, the Baggage APIs and implementation could be extended to +support the use cases Context-scoped attributes are meant to solve. This would require: + +* Having metadata on the baggage entry to decide if it should be a "real baggage" + or a telemetry attribute. This would decide whether the baggage entry is added + to telemetry items, and whether it is (not) propagated. These could be separate + flags, potentially enabling more use cases. +* Changing baggage entries to hold an [Attribute](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.12.0/specification/common/README.md#attribute) instead of a string as value. +* Defining and implementing an association between baggage and telemetry items + (this point would be more or less the same as what is required for the proposed OTEP, e.g. + a span processor could be added that enumerates the baggage, filters for attributes). +* Ideally, protecting baggage entries that are meant for telemetry-usage instead + of app-usage from being read back by the application. + +Using baggage as a vehicle for implementing Context-scoped attributes seems like a valid approach, +the decision could be left up to the language. Whether this is sensible could depend, for exmample, on future +features that baggage might gain anyway (for example, a flag to stop propagation might be useful +for interoperation with OpenCensus), and on how much boilerplate code is required in the language for implementing +a new API vs. extending an existing one. + ## Open questions None known at the moment (but see [Trade-offs and mitigations](#trade-offs-and-mitigations) and [Prior art and alternatives](#prior-art-and-alternatives)). From a425297636550c8b56a25019f6b238bcd93c7fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 20 Sep 2022 14:57:13 +0200 Subject: [PATCH 11/11] Clarify cross-service propagation (there is none) In-reply-to: https://github.com/open-telemetry/oteps/pull/207#discussion_r974378921 --- text/0207-context-scoped-attributes.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/text/0207-context-scoped-attributes.md b/text/0207-context-scoped-attributes.md index c61b90f7d..f9e40d328 100644 --- a/text/0207-context-scoped-attributes.md +++ b/text/0207-context-scoped-attributes.md @@ -1,6 +1,7 @@ # Context-scoped attributes Add Context-scoped telemetry attributes which typically apply to all signals associated with a trace as it crosses a single service. + ## Motivation This OTEP aims to address various related demands that have been brought up in the past, where the scope of resource attributes is too broad, but the scope of span attributes is too narrow. For example, this happens where there is a mismatch between the OpenTelemetry SDK’s (and thus TracerProvider’s, MeterProvider’s) process-wide initialization and the semantic scope of a (sub)service. @@ -35,10 +36,12 @@ This motivation analogously also applies to other signals (metrics, logs) which ## Explanation -The context-scoped attributes allows you to attach attributes to all telemetry signals emitted within a Context (or, following the usual rules of Context values, any child context thereof unless overridden). Context-scoped attributes are normal attributes, which means you can use strings, integers, floating point numbers, booleans or arrays thereof, just like for span or resource attributes. Context-scoped attributes are associated with all telemetry signals emitted while the Context containing the Context-scoped attributes is active and are available to telemetry exporters. For spans, the context within which the span is started applies. Like other telemetry APIs, Context-scoped attributes are write-only for applications. You cannot query the currently set Context-scoped attributes, they are only available on the SDK level (e.g. to telemetry exporters, Samplers and SpanProcessors). +The context-scoped attributes allows you to attach attributes to all telemetry signals emitted within a Context (or, following the usual rules of Context values, any child context thereof unless overridden). Context-scoped attributes are normal attributes, which means you can use strings, integers, floating point numbers, booleans or arrays thereof, just like for span or resource attributes. Context-scoped attributes are associated with all telemetry signals emitted while the Context containing the Context-scoped attributes is active and are available to telemetry exporters. For spans, the context within which the span is started applies. Like other telemetry APIs, Context-scoped attributes are write-only for applications. You cannot query the currently set Context-scoped attributes, they are only available on the SDK level (e.g. to telemetry exporters, Samplers and SpanProcessors). Context-scoped attributes should be thought of equivalent to adding the attribute directly to each single telemetry item it applies to. +Context-scoped attributes MUST NOT be propagated cross-service, no context propagator should be implemented for them. This is because they are meant to annotate (a subset of) the spans of a single service (see also [the next section](#comp-baggage)). + ### Comparing Context-scoped attributes to Baggage