1+ from ddtrace .internal .process_tags import normalize_tag
2+ from ddtrace .internal .process_tags import process_tags
3+ from ddtrace .internal .constants import PROCESS_TAGS
4+ from ddtrace .internal .process_tags .constants import ENTRYPOINT_BASEDIR_TAG , ENTRYPOINT_NAME_TAG , ENTRYPOINT_TYPE_SCRIPT , ENTRYPOINT_TYPE_TAG , ENTRYPOINT_WORKDIR_TAG
5+ from tests .utils import TracerTestCase
6+ from tests .utils import override_env
7+ import sys
8+ import os
9+ from pathlib import Path
10+
11+ def test_normalize_tag ():
12+ assert normalize_tag ("HelloWorld" ) == "helloworld"
13+ assert normalize_tag ("Hello@World!" ) == "hello_world_"
14+ assert normalize_tag ("HeLLo123" ) == "hello123"
15+ assert normalize_tag ("hello world" ) == "hello_world"
16+ assert normalize_tag ("a/b.c_d-e" ) == "a/b.c_d-e"
17+ assert normalize_tag ("héllø" ) == "h_ll_"
18+ assert normalize_tag ("" ) == ""
19+ assert normalize_tag ("💡⚡️" ) == "___"
20+ assert normalize_tag ("!foo@" ) == "_foo_"
21+ assert normalize_tag ("123_abc.DEF-ghi/jkl" ) == "123_abc.def-ghi/jkl"
22+ assert normalize_tag ("Env:Prod-Server#1" ) == "env_prod-server_1"
23+
24+ class TestProcessTags (TracerTestCase ):
25+
26+ def test_process_tags_deactivated (self ):
27+ with self .tracer .trace ("test" ):
28+ pass
29+
30+ span = self .get_spans ()[0 ]
31+ assert span is not None
32+ assert PROCESS_TAGS not in span ._meta
33+
34+ def test_process_tags_activated_with_override_env (self ):
35+ """Test process tags using override_env instead of run_in_subprocess"""
36+ with override_env (dict (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = "True" )):
37+ process_tags ._enabled = True
38+ process_tags .reload ()
39+
40+ with self .tracer .trace ("test" ):
41+ pass
42+
43+ process_tags ._enabled = False
44+
45+ span = self .get_spans ()[0 ]
46+ assert span is not None
47+ assert PROCESS_TAGS in span ._meta
48+
49+ expected_name = "pytest"
50+ expected_type = ENTRYPOINT_TYPE_SCRIPT
51+ expected_basedir = Path (sys .argv [0 ]).resolve ().parent .name
52+ expected_workdir = os .path .basename (os .getcwd ())
53+
54+ serialized_tags = span ._meta [PROCESS_TAGS ]
55+ expected_raw = (
56+ f"{ ENTRYPOINT_WORKDIR_TAG } :{ expected_workdir } ,"
57+ f"{ ENTRYPOINT_BASEDIR_TAG } :{ expected_basedir } ,"
58+ f"{ ENTRYPOINT_NAME_TAG } :{ expected_name } ,"
59+ f"{ ENTRYPOINT_TYPE_TAG } :{ expected_type } "
60+ )
61+ assert serialized_tags == expected_raw
62+
63+ tags_dict = dict (tag .split (":" , 1 ) for tag in serialized_tags .split ("," ))
64+ assert tags_dict [ENTRYPOINT_NAME_TAG ] == expected_name
65+ assert tags_dict [ENTRYPOINT_TYPE_TAG ] == expected_type
66+ assert tags_dict [ENTRYPOINT_BASEDIR_TAG ] == expected_basedir
67+ assert tags_dict [ENTRYPOINT_WORKDIR_TAG ] == expected_workdir
68+
69+ def test_process_tags_only_on_local_root_span (self ):
70+ """Test that only local root spans get process tags, not children"""
71+ with override_env (dict (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = "True" )):
72+ process_tags ._enabled = True
73+ process_tags .reload ()
74+
75+ with self .tracer .trace ("parent" ):
76+ with self .tracer .trace ("child" ):
77+ pass
78+
79+ process_tags ._enabled = False
80+
81+ spans = self .get_spans ()
82+ assert len (spans ) == 2
83+
84+ parent = [s for s in spans if s .name == "parent" ][0 ]
85+ assert PROCESS_TAGS in parent ._meta
86+
87+ child = [s for s in spans if s .name == "child" ][0 ]
88+ assert PROCESS_TAGS not in child ._meta
89+
90+ def test_add_process_tag_compute_exception (self ):
91+ """Test error handling when compute raises exception"""
92+ with override_env (dict (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = "True" )):
93+ process_tags ._enabled = True
94+ process_tags .reload ()
95+
96+ def failing_compute ():
97+ raise ValueError ("Test exception" )
98+
99+ process_tags .add_process_tag ("test.tag" , compute = failing_compute )
100+
101+ assert "test.tag" not in process_tags .process_tags
102+
103+ process_tags .add_process_tag ("test.working" , value = "value" )
104+ assert "test.working" in process_tags .process_tags
105+ assert process_tags .process_tags ["test.working" ] == "value"
106+
107+ process_tags ._enabled = False
108+
109+ def test_serialization_caching (self ):
110+ """Test that serialization is cached and invalidated properly"""
111+ with override_env (dict (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = "True" )):
112+ process_tags ._enabled = True
113+ process_tags .reload ()
114+
115+ result1 = process_tags .get_serialized_process_tags ()
116+ assert process_tags ._serialized is not None
117+
118+ process_tags .add_process_tag ("custom.tag" , value = "test" )
119+ assert process_tags ._serialized is None
120+
121+ result3 = process_tags .get_serialized_process_tags ()
122+ assert "custom.tag:test" in result3
123+
124+ process_tags ._enabled = False
0 commit comments