-
Notifications
You must be signed in to change notification settings - Fork 469
chore(internal): add process_tags to APM payload #15146
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
Open
dubloom
wants to merge
32
commits into
main
Choose a base branch
from
dubloom/process-tags-collection
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+507
−0
Open
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
d76a11d
chore(internal): add process_tags to first span of each payload
dubloom a3643d8
tests(process_tags): add tests
dubloom a52e8cc
lint
dubloom f943f2a
fix: suitespec
dubloom 660bd64
fix: telemetry test
dubloom ace7fae
Merge branch 'main' into dubloom/process-tags-collection
dubloom 78dd521
fix telemetry 2
dubloom dd58490
simplify process_tags (brett review)
dubloom f47539e
Merge branch 'main' into dubloom/process-tags-collection
dubloom 184ef53
update python version
dubloom be2973e
put tests within internal suite
dubloom c6b4d7f
remove sys hack
dubloom c6cb1be
make tests compatible with CI
dubloom 974b474
Merge branch 'main' into dubloom/process-tags-collection
dubloom 7416466
lint
dubloom 0428dcd
brett review
dubloom b66d6a4
Merge branch 'main' into dubloom/process-tags-collection
dubloom 71b5ed7
Merge branch 'main' into dubloom/process-tags-collection
dubloom a123350
Merge branch 'main' into dubloom/process-tags-collection
dubloom 32ddf35
improve tag normalization
dubloom ee77b0e
Merge branch 'main' into dubloom/process-tags-collection
dubloom f5c3eee
Merge branch 'main' into dubloom/process-tags-collection
dubloom 7cf4143
gab review
dubloom ae20207
improving normalization
dubloom f70b7ed
Merge branch 'main' into dubloom/process-tags-collection
dubloom efe28fa
remove print
dubloom f957552
Update tests/internal/test_process_tags.py
dubloom 86f04db
Merge branch 'main' into dubloom/process-tags-collection
dubloom 914d52a
add a test that activates the feature with env variable
dubloom fdd6480
chore(di): add process_tags (#15225)
dubloom fa3dbb8
Merge branch 'main' into dubloom/process-tags-collection
dubloom aa23d07
lint
dubloom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # | ||
| # This file is autogenerated by pip-compile with Python 3.13 | ||
| # by the following command: | ||
| # | ||
| # pip-compile --allow-unsafe --no-annotate .riot/requirements/1645326.in | ||
| # | ||
| attrs==25.4.0 | ||
| coverage[toml]==7.11.0 | ||
| hypothesis==6.45.0 | ||
| iniconfig==2.3.0 | ||
| mock==5.2.0 | ||
| opentracing==2.4.0 | ||
| packaging==25.0 | ||
| pluggy==1.6.0 | ||
| pygments==2.19.2 | ||
| pytest==8.4.2 | ||
| pytest-cov==7.0.0 | ||
| pytest-mock==3.15.1 | ||
| sortedcontainers==2.4.0 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import os | ||
| from pathlib import Path | ||
| import re | ||
| import sys | ||
| from typing import Callable | ||
| from typing import Dict | ||
| from typing import Optional | ||
|
|
||
| from ddtrace.internal.forksafe import Lock | ||
| from ddtrace.internal.logger import get_logger | ||
| from ddtrace.settings._config import config | ||
|
|
||
| from .constants import ENTRYPOINT_BASEDIR_TAG | ||
| from .constants import ENTRYPOINT_NAME_TAG | ||
| from .constants import ENTRYPOINT_TYPE_SCRIPT | ||
| from .constants import ENTRYPOINT_TYPE_TAG | ||
| from .constants import ENTRYPOINT_WORKDIR_TAG | ||
|
|
||
|
|
||
| log = get_logger(__name__) | ||
|
|
||
|
|
||
| # outside of ProcessTags class for test purpose | ||
| def normalize_tag(value: str) -> str: | ||
dubloom marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return re.sub(r"[^a-z0-9/._-]", "_", value.lower()) | ||
dubloom marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| class ProcessTags: | ||
dubloom marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| def __init__(self) -> None: | ||
| self._lock = Lock() | ||
| self._serialized: Optional[str] = None | ||
| self._enabled = config._process_tags_enabled | ||
| self._process_tags: Dict[str, str] = {} | ||
| self.reload() | ||
|
|
||
| def _serialize_process_tags(self) -> Optional[str]: | ||
| if self._process_tags and not self._serialized: | ||
| serialized_tags = ",".join(f"{key}:{value}" for key, value in self._process_tags.items()) | ||
| return serialized_tags | ||
| return None | ||
|
|
||
| def get_serialized_process_tags(self) -> Optional[str]: | ||
dubloom marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if not self._enabled: | ||
| return None | ||
|
|
||
| with self._lock: | ||
| if not self._serialized: | ||
| self._serialized = self._serialize_process_tags() | ||
| return self._serialized | ||
|
|
||
| def add_process_tag(self, key: str, value: Optional[str] = None, compute: Optional[Callable[[], str]] = None): | ||
| if not self._enabled: | ||
| return | ||
|
|
||
| if compute: | ||
| try: | ||
| value = compute() | ||
| except Exception as e: | ||
| log.debug("failed to set %s process_tag: %s", key, e) | ||
|
|
||
| if value: | ||
| with self._lock: | ||
| self._process_tags[key] = normalize_tag(value) | ||
| self._serialized = None | ||
|
|
||
| def reload(self): | ||
| if not self._enabled: | ||
| return | ||
|
|
||
| with self._lock: | ||
| self._process_tags = {} | ||
|
|
||
| self.add_process_tag(ENTRYPOINT_WORKDIR_TAG, compute=lambda: os.path.basename(os.getcwd())) | ||
| self.add_process_tag(ENTRYPOINT_BASEDIR_TAG, compute=lambda: Path(sys.argv[0]).resolve().parent.name) | ||
| self.add_process_tag(ENTRYPOINT_NAME_TAG, compute=lambda: os.path.splitext(os.path.basename(sys.argv[0]))[0]) | ||
| self.add_process_tag(ENTRYPOINT_TYPE_TAG, value=ENTRYPOINT_TYPE_SCRIPT) | ||
|
|
||
|
|
||
| process_tags = ProcessTags() | ||
dubloom marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ENTRYPOINT_NAME_TAG = "entrypoint.name" | ||
| ENTRYPOINT_WORKDIR_TAG = "entrypoint.workdir" | ||
|
|
||
| ENTRYPOINT_TYPE_TAG = "entrypoint.type" | ||
| ENTRYPOINT_TYPE_SCRIPT = "script" | ||
|
|
||
| ENTRYPOINT_BASEDIR_TAG = "entrypoint.basedir" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import os | ||
dubloom marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| from pathlib import Path | ||
| import sys | ||
|
|
||
| from ddtrace.internal.constants import PROCESS_TAGS | ||
| from ddtrace.internal.process_tags import normalize_tag | ||
| from ddtrace.internal.process_tags import process_tags | ||
| from ddtrace.internal.process_tags.constants import ENTRYPOINT_BASEDIR_TAG | ||
| from ddtrace.internal.process_tags.constants import ENTRYPOINT_NAME_TAG | ||
| from ddtrace.internal.process_tags.constants import ENTRYPOINT_TYPE_SCRIPT | ||
| from ddtrace.internal.process_tags.constants import ENTRYPOINT_TYPE_TAG | ||
| from ddtrace.internal.process_tags.constants import ENTRYPOINT_WORKDIR_TAG | ||
| from tests.utils import TracerTestCase | ||
| from tests.utils import override_env | ||
|
|
||
|
|
||
| def test_normalize_tag(): | ||
| assert normalize_tag("HelloWorld") == "helloworld" | ||
dubloom marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| assert normalize_tag("Hello@World!") == "hello_world_" | ||
| assert normalize_tag("HeLLo123") == "hello123" | ||
| assert normalize_tag("hello world") == "hello_world" | ||
| assert normalize_tag("a/b.c_d-e") == "a/b.c_d-e" | ||
| assert normalize_tag("héllø") == "h_ll_" | ||
| assert normalize_tag("") == "" | ||
| assert normalize_tag("💡⚡️") == "___" | ||
| assert normalize_tag("!foo@") == "_foo_" | ||
| assert normalize_tag("123_abc.DEF-ghi/jkl") == "123_abc.def-ghi/jkl" | ||
| assert normalize_tag("Env:Prod-Server#1") == "env_prod-server_1" | ||
|
|
||
|
|
||
| class TestProcessTags(TracerTestCase): | ||
| def test_process_tags_deactivated(self): | ||
| with self.tracer.trace("test"): | ||
| pass | ||
|
|
||
| span = self.get_spans()[0] | ||
| assert span is not None | ||
| assert PROCESS_TAGS not in span._meta | ||
|
|
||
| def test_process_tags_activated_with_override_env(self): | ||
| """Test process tags using override_env instead of run_in_subprocess""" | ||
| with override_env(dict(DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED="True")): | ||
| process_tags._enabled = True | ||
| process_tags.reload() | ||
|
|
||
| with self.tracer.trace("test"): | ||
| pass | ||
|
|
||
| process_tags._enabled = False | ||
|
|
||
| span = self.get_spans()[0] | ||
| assert span is not None | ||
| assert PROCESS_TAGS in span._meta | ||
|
|
||
| expected_name = "pytest" | ||
| expected_type = ENTRYPOINT_TYPE_SCRIPT | ||
| expected_basedir = Path(sys.argv[0]).resolve().parent.name | ||
| expected_workdir = os.path.basename(os.getcwd()) | ||
|
|
||
| serialized_tags = span._meta[PROCESS_TAGS] | ||
| expected_raw = ( | ||
| f"{ENTRYPOINT_WORKDIR_TAG}:{expected_workdir}," | ||
| f"{ENTRYPOINT_BASEDIR_TAG}:{expected_basedir}," | ||
| f"{ENTRYPOINT_NAME_TAG}:{expected_name}," | ||
| f"{ENTRYPOINT_TYPE_TAG}:{expected_type}" | ||
| ) | ||
| assert serialized_tags == expected_raw | ||
|
|
||
| tags_dict = dict(tag.split(":", 1) for tag in serialized_tags.split(",")) | ||
| assert tags_dict[ENTRYPOINT_NAME_TAG] == expected_name | ||
| assert tags_dict[ENTRYPOINT_TYPE_TAG] == expected_type | ||
| assert tags_dict[ENTRYPOINT_BASEDIR_TAG] == expected_basedir | ||
| assert tags_dict[ENTRYPOINT_WORKDIR_TAG] == expected_workdir | ||
|
|
||
| def test_process_tags_only_on_local_root_span(self): | ||
| """Test that only local root spans get process tags, not children""" | ||
| with override_env(dict(DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED="True")): | ||
| process_tags._enabled = True | ||
| process_tags.reload() | ||
|
|
||
| with self.tracer.trace("parent"): | ||
| with self.tracer.trace("child"): | ||
| pass | ||
|
|
||
| process_tags._enabled = False | ||
|
|
||
| spans = self.get_spans() | ||
| assert len(spans) == 2 | ||
|
|
||
| parent = [s for s in spans if s.name == "parent"][0] | ||
| assert PROCESS_TAGS in parent._meta | ||
|
|
||
| child = [s for s in spans if s.name == "child"][0] | ||
| assert PROCESS_TAGS not in child._meta | ||
|
|
||
| def test_add_process_tag_compute_exception(self): | ||
| """Test error handling when compute raises exception""" | ||
| with override_env(dict(DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED="True")): | ||
| process_tags._enabled = True | ||
| process_tags.reload() | ||
|
|
||
| def failing_compute(): | ||
| raise ValueError("Test exception") | ||
|
|
||
| process_tags.add_process_tag("test.tag", compute=failing_compute) | ||
|
|
||
| assert "test.tag" not in process_tags._process_tags | ||
|
|
||
| process_tags.add_process_tag("test.working", value="value") | ||
| assert "test.working" in process_tags._process_tags | ||
| assert process_tags._process_tags["test.working"] == "value" | ||
|
|
||
| process_tags._enabled = False | ||
|
|
||
| def test_serialization_caching(self): | ||
| """Test that serialization is cached and invalidated properly""" | ||
| with override_env(dict(DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED="True")): | ||
| process_tags._enabled = True | ||
| process_tags.reload() | ||
|
|
||
| process_tags.get_serialized_process_tags() | ||
| assert process_tags._serialized is not None | ||
|
|
||
| process_tags.add_process_tag("custom.tag", value="test") | ||
| assert process_tags._serialized is None | ||
|
|
||
| result3 = process_tags.get_serialized_process_tags() | ||
| assert "custom.tag:test" in result3 | ||
|
|
||
| process_tags._enabled = False | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.