Skip to content

Commit 7256443

Browse files
fix(lib-injection): ensure sitecustomize.py supports Python 2.7+ [backport 2.17] (#11441)
Co-authored-by: Brett Langdon <[email protected]>
1 parent 7ec8701 commit 7256443

File tree

3 files changed

+106
-25
lines changed

3 files changed

+106
-25
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Lib-injection tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
test_sitecustomize:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python:
15+
# requires openssl 1.0, which is hard to get
16+
# - "2.6"
17+
# - "3.4"
18+
# segfaults
19+
# - 3.0"
20+
# - 3.1"
21+
# - "3.2"
22+
# - "3.3"
23+
- "2.7"
24+
- "3.5"
25+
- "3.6"
26+
- "3.7"
27+
- "3.8"
28+
- "3.9"
29+
- "3.10"
30+
- "3.11"
31+
- "3.12"
32+
- "3.13"
33+
steps:
34+
- uses: actions/checkout@v4
35+
- name: Install pyenv
36+
run: |
37+
export PYENV_ROOT="${HOME}/.pyenv"
38+
export PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"
39+
PYENV_GIT_TAG=main curl https://pyenv.run | bash
40+
echo "PYENV_ROOT=${PYENV_ROOT}" >> $GITHUB_ENV
41+
echo "PATH=${PATH}" >> $GITHUB_ENV
42+
- name: Install python ${{ matrix.python }}
43+
run: |
44+
which pyenv
45+
pyenv --version
46+
pyenv install "${{ matrix.python }}" && pyenv global "${{ matrix.python }}"
47+
- name: Print Python version
48+
run: python --version
49+
- name: Validate sitecustomize.py runs with ${{ matrix.python }}
50+
run: python lib-injection/sources/sitecustomize.py

lib-injection/sources/sitecustomize.py

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,49 @@
55

66
from collections import namedtuple
77
import csv
8-
import importlib.util
98
import json
109
import os
1110
import platform
1211
import re
1312
import subprocess
1413
import sys
1514
import time
16-
from typing import Tuple
1715

1816

1917
Version = namedtuple("Version", ["version", "constraint"])
2018

2119

22-
def parse_version(version: str) -> Tuple:
23-
constraint_idx = re.search(r"\d", version).start()
24-
numeric = version[constraint_idx:]
25-
constraint = version[:constraint_idx]
26-
parsed_version = tuple(int(re.sub("[^0-9]", "", p)) for p in numeric.split("."))
27-
return Version(parsed_version, constraint)
20+
def parse_version(version):
21+
try:
22+
constraint_match = re.search(r"\d", version)
23+
if not constraint_match:
24+
return Version((0, 0), "")
25+
constraint_idx = constraint_match.start()
26+
numeric = version[constraint_idx:]
27+
constraint = version[:constraint_idx]
28+
parsed_version = tuple(int(re.sub("[^0-9]", "", p)) for p in numeric.split("."))
29+
return Version(parsed_version, constraint)
30+
except Exception:
31+
return Version((0, 0), "")
2832

2933

3034
SCRIPT_DIR = os.path.dirname(__file__)
3135
RUNTIMES_ALLOW_LIST = {
32-
"cpython": {"min": parse_version("3.7"), "max": parse_version("3.13")},
36+
"cpython": {
37+
"min": Version(version=(3, 7), constraint=""),
38+
"max": Version(version=(3, 13), constraint=""),
39+
}
3340
}
3441

3542
FORCE_INJECT = os.environ.get("DD_INJECT_FORCE", "").lower() in ("true", "1", "t")
3643
FORWARDER_EXECUTABLE = os.environ.get("DD_TELEMETRY_FORWARDER_PATH", "")
3744
TELEMETRY_ENABLED = "DD_INJECTION_ENABLED" in os.environ
3845
DEBUG_MODE = os.environ.get("DD_TRACE_DEBUG", "").lower() in ("true", "1", "t")
39-
INSTALLED_PACKAGES = None
40-
PYTHON_VERSION = None
41-
PYTHON_RUNTIME = None
42-
PKGS_ALLOW_LIST = None
43-
EXECUTABLES_DENY_LIST = None
46+
INSTALLED_PACKAGES = {}
47+
PYTHON_VERSION = "unknown"
48+
PYTHON_RUNTIME = "unknown"
49+
PKGS_ALLOW_LIST = {}
50+
EXECUTABLES_DENY_LIST = set()
4451
VERSION_COMPAT_FILE_LOCATIONS = (
4552
os.path.abspath(os.path.join(SCRIPT_DIR, "../datadog-lib/min_compatible_versions.csv")),
4653
os.path.abspath(os.path.join(SCRIPT_DIR, "min_compatible_versions.csv")),
@@ -103,7 +110,7 @@ def build_denied_executables():
103110
cleaned = line.strip("\n")
104111
denied_executables.add(cleaned)
105112
denied_executables.add(os.path.basename(cleaned))
106-
_log(f"Built denied-executables list of {len(denied_executables)} entries", level="debug")
113+
_log("Built denied-executables list of %s entries" % (len(denied_executables),), level="debug")
107114
return denied_executables
108115

109116

@@ -143,9 +150,15 @@ def send_telemetry(event):
143150
stderr=subprocess.PIPE,
144151
universal_newlines=True,
145152
)
146-
p.stdin.write(event_json)
147-
p.stdin.close()
148-
_log("wrote telemetry to %s" % FORWARDER_EXECUTABLE, level="debug")
153+
if p.stdin:
154+
p.stdin.write(event_json)
155+
p.stdin.close()
156+
_log("wrote telemetry to %s" % FORWARDER_EXECUTABLE, level="debug")
157+
else:
158+
_log(
159+
"failed to write telemetry to %s, could not write to telemetry writer stdin" % FORWARDER_EXECUTABLE,
160+
level="error",
161+
)
149162

150163

151164
def _get_clib():
@@ -154,7 +167,7 @@ def _get_clib():
154167
If GNU is not detected then returns MUSL.
155168
"""
156169

157-
libc, version = platform.libc_ver()
170+
libc, _ = platform.libc_ver()
158171
if libc == "glibc":
159172
return "gnu"
160173
return "musl"
@@ -170,7 +183,9 @@ def _log(msg, *args, **kwargs):
170183
if DEBUG_MODE:
171184
asctime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
172185
msg = "[%s] [%s] datadog.autoinstrumentation(pid: %d): " % (asctime, level.upper(), os.getpid()) + msg % args
173-
print(msg, file=sys.stderr)
186+
sys.stderr.write(msg)
187+
sys.stderr.write("\n")
188+
sys.stderr.flush()
174189

175190

176191
def runtime_version_is_supported(python_runtime, python_version):
@@ -197,13 +212,13 @@ def get_first_incompatible_sysarg():
197212
_log("sys.argv not available, skipping sys.argv check", level="debug")
198213
return
199214

200-
_log(f"Checking sysargs: len(argv): {len(sys.argv)}", level="debug")
215+
_log("Checking sys.args: len(sys.argv): %s" % (len(sys.argv),), level="debug")
201216
if len(sys.argv) <= 1:
202217
return
203218
argument = sys.argv[0]
204-
_log(f"Is argument {argument} in deny-list?", level="debug")
219+
_log("Is argument %s in deny-list?" % (argument,), level="debug")
205220
if argument in EXECUTABLES_DENY_LIST or os.path.basename(argument) in EXECUTABLES_DENY_LIST:
206-
_log(f"argument {argument} is in deny-list", level="debug")
221+
_log("argument %s is in deny-list" % (argument,), level="debug")
207222
return argument
208223

209224

@@ -225,7 +240,13 @@ def _inject():
225240
os.environ["_DD_INJECT_WAS_ATTEMPTED"] = "true"
226241
spec = None
227242
try:
243+
# `find_spec` is only available in Python 3.4+
244+
# https://docs.python.org/3/library/importlib.html#importlib.util.find_spec
245+
# DEV: It is ok to fail here on import since it'll only fail on Python versions we don't support / inject into
246+
import importlib.util
247+
228248
# None is a valid return value for find_spec (module was not found), so we need to check for it explicitly
249+
229250
spec = importlib.util.find_spec("ddtrace")
230251
if not spec:
231252
raise ModuleNotFoundError("ddtrace")
@@ -377,5 +398,11 @@ def _inject():
377398

378399
try:
379400
_inject()
380-
except Exception:
381-
pass # absolutely never allow exceptions to propagate to the app
401+
except Exception as e:
402+
try:
403+
event = gen_telemetry_payload(
404+
[create_count_metric("library_entrypoint.error", ["error_type:" + type(e).__name__.lower()])]
405+
)
406+
send_telemetry(event)
407+
except Exception:
408+
pass # absolutely never allow exceptions to propagate to the app
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
lib-injection: Support Python 2.7+ for injection compatibility check.

0 commit comments

Comments
 (0)