Skip to content

[carriers] add env carrier #4609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions opentelemetry-api/src/opentelemetry/propagators/envcarrier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os
import typing
from opentelemetry.propagators.textmap import Getter, Setter

class EnvironmentGetter(Getter[dict]):
"""This class decorates Getter to enable extracting from context and baggage
from environment variables.
"""

KEY_MAPPING = {
"TRACEPARENT": "traceparent",
"TRACESTATE": "tracestate",
"BAGGAGE": "baggage"
}
Comment on lines +10 to +14
Copy link
Member

@pellared pellared Jun 3, 2025

Choose a reason for hiding this comment

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

Why do we have a special handling only for these 3 keys?
Couldn't 't it be a generic rule how the env carrier works?

Copy link
Author

Choose a reason for hiding this comment

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

I've removed them and pushed the change. They were there from my working prototype enforcing on those were set. However, in the specification we do outline that those are the three well-known values that SHOULD be used. Ultimately, it doesn't matter. Any keys in the environment, when lowered, will only be considered valid context in the text map propagator when they match one of the well-known specs supported in the SDK. (b3/w3c/etc)


def __init__(self):
self.env_copy = dict(os.environ)
self.carrier = {}

for env_key, env_value in self.env_copy.items():
if env_key in self.KEY_MAPPING:
self.carrier[self.KEY_MAPPING[env_key]] = env_value
else:
self.carrier[env_key] = env_value

def get(self, carrier: dict, key: str) -> typing.Optional[typing.List[str]]:
"""Get a value from the carrier for the given key"""
val = self.carrier.get(key, None)
if val is None:
return None
if isinstance(val, typing.Iterable) and not isinstance(val, str):
return list(val)
return [val]

def keys(self, carrier: dict) -> typing.List[str]:
"""Get all keys from the carrier"""
return list(self.carrier.keys())

class EnvironmentSetter(Setter[dict]):
"""This class decorates Setter to enable setting context and baggage
to environment variables.
Comment on lines +40 to +41
Copy link
Member

@pellared pellared Jun 3, 2025

Choose a reason for hiding this comment

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

Isn't the purpose to set env var for some external process that is going to be spawned?

From https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/env-carriers.md#environment-variable-immutability and https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/env-carriers.md#process-spawning:

Applications SHOULD NOT modify context-related environment variables of the environment in which the parent process exists.

Parent processes SHOULD copy the current environment variables (if applicable), modify, and inject context when spawning child processes.

Copy link
Author

Choose a reason for hiding this comment

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

yes! and i'm not sure how best to handle that implementation detail in practice at the carrier level, which is why this is the current set.

In my head, the only real path forward I see with that portion is to set a dictionary with that new context, and let the implementer use that returned dictionary when they do a popen run like the below psuedo code:

# somehow the user sets traceparent, tracestate, and baggage and setter returns an env dictionary instead of what it does now

# the user then takes that and sets it in popen
popen.run(["something"], env=env_dict_from_setter)

"""

KEY_MAPPING = {
"TRACEPARENT": "traceparent",
"TRACESTATE": "tracestate",
"BAGGAGE": "baggage"
}

def set(self, carrier: typing.Optional[dict], key: str, value: str) -> None:
"""Set a value in the environment for the given key.

Args:
carrier: Not used for environment setter, but kept for interface compatibility
key: The key to set
value: The value to set
"""
env_key = self.KEY_MAPPING.get(key, key.upper())

os.environ[env_key] = value
Loading