diff --git a/ddtrace/internal/datastreams/processor.py b/ddtrace/internal/datastreams/processor.py index 5e1bf4912fc..1ea6f15a7ed 100644 --- a/ddtrace/internal/datastreams/processor.py +++ b/ddtrace/internal/datastreams/processor.py @@ -291,7 +291,8 @@ def periodic(self) -> None: raw_payload["Env"] = compat.ensure_text(config.env) if config.version: raw_payload["Version"] = compat.ensure_text(config.version) - if p_tags := process_tags.process_tags_list: + process_tags._set_globals() + if p_tags := process_tags.process_tags_list: # type: ignore[attr-defined] raw_payload["ProcessTags"] = p_tags payload = packb(raw_payload) diff --git a/ddtrace/internal/process_tags/__init__.py b/ddtrace/internal/process_tags/__init__.py index 47ff17c2d6c..0631a6de2b0 100644 --- a/ddtrace/internal/process_tags/__init__.py +++ b/ddtrace/internal/process_tags/__init__.py @@ -18,6 +18,8 @@ ENTRYPOINT_TYPE_TAG = "entrypoint.type" ENTRYPOINT_TYPE_SCRIPT = "script" ENTRYPOINT_BASEDIR_TAG = "entrypoint.basedir" +SVC_USER_TAG = "svc.user" +SVC_AUTO_TAG = "svc.auto" _CONSECUTIVE_UNDERSCORES_PATTERN = re.compile(r"_{2,}") _ALLOWED_CHARS = _ALLOWED_CHARS = frozenset("abcdefghijklmnopqrstuvwxyz0123456789/._-") @@ -62,11 +64,15 @@ def generate_process_tags() -> tuple[Optional[str], Optional[list[str]]]: if not config.enabled: return None, None + from ddtrace import config as ddtrace_config + tag_definitions = [ (ENTRYPOINT_WORKDIR_TAG, lambda: os.path.basename(os.getcwd())), (ENTRYPOINT_BASEDIR_TAG, lambda: Path(sys.argv[0]).resolve().parent.name), (ENTRYPOINT_NAME_TAG, lambda: os.path.splitext(os.path.basename(sys.argv[0]))[0]), (ENTRYPOINT_TYPE_TAG, lambda: ENTRYPOINT_TYPE_SCRIPT), + (SVC_USER_TAG, lambda: "true" if ddtrace_config._is_user_provided_service else None), + (SVC_AUTO_TAG, lambda: ddtrace_config.service if not ddtrace_config._is_user_provided_service else None), ] process_tags_list = sorted( @@ -93,4 +99,11 @@ def compute_base_hash(container_tags_hash): base_hash, base_hash_bytes = None, b"" -process_tags, process_tags_list = generate_process_tags() +process_tags = None + + +def _set_globals(): + global process_tags + global process_tags_list + if not process_tags: + process_tags, process_tags_list = generate_process_tags() diff --git a/ddtrace/internal/remoteconfig/client.py b/ddtrace/internal/remoteconfig/client.py index f4c5b773885..2b3927c3e61 100644 --- a/ddtrace/internal/remoteconfig/client.py +++ b/ddtrace/internal/remoteconfig/client.py @@ -239,7 +239,8 @@ def __init__(self) -> None: tags=[":".join(_) for _ in tags.items()], ) - if p_tags_list := process_tags.process_tags_list: + process_tags._set_globals() + if p_tags_list := process_tags.process_tags_list: # type: ignore[attr-defined] self._client_tracer["process_tags"] = p_tags_list self.cached_target_files: list[AppliedConfigType] = [] diff --git a/ddtrace/internal/runtime/tag_collectors.py b/ddtrace/internal/runtime/tag_collectors.py index 29b59867f5d..65a4591308f 100644 --- a/ddtrace/internal/runtime/tag_collectors.py +++ b/ddtrace/internal/runtime/tag_collectors.py @@ -103,5 +103,6 @@ class ProcessTagCollector(RuntimeTagCollector): def collect_fn(self, keys): # DEV: we do not access direct process_tags_list so we can # reload it in the tests + process_tags._set_globals() process_tags_list = process_tags.process_tags_list return process_tags_list or [] diff --git a/ddtrace/internal/telemetry/data.py b/ddtrace/internal/telemetry/data.py index 5ffa2efe66a..dbbf5c36968 100644 --- a/ddtrace/internal/telemetry/data.py +++ b/ddtrace/internal/telemetry/data.py @@ -63,6 +63,8 @@ def _get_application(key: tuple[str, str, str]) -> dict: "runtime_version": _format_version_info(sys.implementation.version), } + process_tags._set_globals() + if p_tags := process_tags.process_tags: application["process_tags"] = p_tags diff --git a/tests/internal/test_process_tags.py b/tests/internal/test_process_tags.py index dc98ca682e8..ca577478150 100644 --- a/tests/internal/test_process_tags.py +++ b/tests/internal/test_process_tags.py @@ -114,6 +114,17 @@ def test_process_tags_activated(self): with self.tracer.trace("child"): pass + @pytest.mark.snapshot + @run_in_subprocess(env_overrides=dict(DD_SERVICE="foobar")) + def test_process_tags_user_defined_service(self): + with patch("sys.argv", [TEST_SCRIPT_PATH]), patch("os.getcwd", return_value=TEST_WORKDIR_PATH): + config.enabled = True # type: ignore[assignment] + process_tag_reload() + + with self.tracer.trace("parent"): + with self.tracer.trace("child"): + pass + @pytest.mark.snapshot def test_process_tags_edge_case(self): with patch("sys.argv", ["/test_script"]), patch("os.getcwd", return_value=TEST_WORKDIR_PATH): diff --git a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_edge_case.json b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_edge_case.json index 634d8e00dbd..123c66f555f 100644 --- a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_edge_case.json +++ b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_edge_case.json @@ -11,7 +11,7 @@ "meta": { "_dd.p.dm": "-0", "_dd.p.tid": "6911da3a00000000", - "_dd.tags.process": "entrypoint.basedir:,entrypoint.name:test_script,entrypoint.type:script,entrypoint.workdir:workdir", + "_dd.tags.process": "entrypoint.basedir:,entrypoint.name:test_script,entrypoint.type:script,entrypoint.workdir:workdir,svc.auto:tests.internal", "language": "python", "runtime-id": "c9342b8003de45feb0bf56d32ece46a1" }, diff --git a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_activated.json b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_activated.json index 8a3c1c18440..a03046072f4 100644 --- a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_activated.json +++ b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_activated.json @@ -11,7 +11,7 @@ "meta": { "_dd.p.dm": "-0", "_dd.p.tid": "6911dc5a00000000", - "_dd.tags.process": "entrypoint.basedir:to,entrypoint.name:test_script,entrypoint.type:script,entrypoint.workdir:workdir", + "_dd.tags.process": "entrypoint.basedir:to,entrypoint.name:test_script,entrypoint.type:script,entrypoint.workdir:workdir,svc.auto:tests.internal", "language": "python", "runtime-id": "2d5de91f8dd9442cad7faca5554a09f1" }, diff --git a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_edge_case.json b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_edge_case.json index 496767f5d36..c6bc5eb0e22 100644 --- a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_edge_case.json +++ b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_edge_case.json @@ -11,7 +11,7 @@ "meta": { "_dd.p.dm": "-0", "_dd.p.tid": "69244c0600000000", - "_dd.tags.process": "entrypoint.name:test_script,entrypoint.type:script,entrypoint.workdir:workdir", + "_dd.tags.process": "entrypoint.name:test_script,entrypoint.type:script,entrypoint.workdir:workdir,svc.auto:tests.internal", "language": "python", "runtime-id": "9f7b2a86304f4a82b0ad44b603ed29a0" }, diff --git a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_error.json b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_error.json index 8310e9dd80e..9e94cbc557f 100644 --- a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_error.json +++ b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_error.json @@ -11,7 +11,7 @@ "meta": { "_dd.p.dm": "-0", "_dd.p.tid": "6924399800000000", - "_dd.tags.process": "entrypoint.type:script,entrypoint.workdir:workdir", + "_dd.tags.process": "entrypoint.type:script,entrypoint.workdir:workdir,svc.auto:tests.internal", "language": "python", "runtime-id": "917df14013db49fdaeea24fe2021f42a" }, diff --git a/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_user_defined_service.json b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_user_defined_service.json new file mode 100644 index 00000000000..d60810a00f4 --- /dev/null +++ b/tests/snapshots/tests.internal.test_process_tags.TestProcessTags.test_process_tags_user_defined_service.json @@ -0,0 +1,38 @@ +[[ + { + "name": "parent", + "service": "foobar", + "resource": "parent", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6997861b00000000", + "_dd.tags.process": "entrypoint.basedir:to,entrypoint.name:test_script,entrypoint.type:script,entrypoint.workdir:workdir,svc.user:true", + "language": "python", + "runtime-id": "9b3d6828daea467087a29c4c44faa317" + }, + "metrics": { + "_dd.top_level": 1.0, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1.0, + "process_id": 54689.0 + }, + "duration": 952166, + "start": 1771537947768385679 + }, + { + "name": "child", + "service": "foobar", + "resource": "child", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "", + "error": 0, + "duration": 61917, + "start": 1771537947769090137 + }]]