forked from instana/python-sensor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathruntime.py
More file actions
212 lines (170 loc) · 7.12 KB
/
Copy pathruntime.py
File metadata and controls
212 lines (170 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# (c) Copyright IBM Corp. 2021
# (c) Copyright Instana Inc. 2020
import os
import platform
import re
import sys
from typing import Dict, List, Tuple, Union
from instana.log import logger
def get_py_source(filename: str) -> Dict[str, str]:
"""
Retrieves the source code for Python files requested by the UI via the host agent.
This function reads and returns the content of Python source files. It validates
that the requested file has a .py extension and returns an appropriate error
message if the file cannot be read or is not a Python file.
Args:
filename (str): The fully qualified path to a Python source file
Returns:
Dict[str, str]: A dictionary containing either:
- {"data": source_code} if successful
- {"error": error_message} if an error occurred
"""
response = None
try:
if regexp_py.search(filename) is None:
response = {"error": "Only Python source files are allowed. (*.py)"}
else:
pysource = ""
with open(filename, 'r') as pyfile:
pysource = pyfile.read()
response = {"data": pysource}
except Exception as exc:
response = {"error": str(exc)}
return response
# Used by get_py_source
regexp_py = re.compile(r"\.py$")
def determine_service_name() -> str:
"""
Determines the most appropriate service name for this application process.
The service name is determined using the following priority order:
1. INSTANA_SERVICE_NAME environment variable if set
2. For specific frameworks:
- For gunicorn: process title or "gunicorn"
- For Flask: FLASK_APP environment variable
- For Django: first part of DJANGO_SETTINGS_MODULE
- For uwsgi: "uWSGI master/worker [app_name]"
3. Command line arguments (first non-option argument)
4. Executable name
5. "python" as a fallback
Returns:
str: The determined service name
"""
# One environment variable to rule them all
if "INSTANA_SERVICE_NAME" in os.environ:
return os.environ["INSTANA_SERVICE_NAME"]
# Now best effort in naming this process. No nice package.json like in Node.js
# so we do best effort detection here.
app_name = "python" # the default name
basename = None
try:
if not hasattr(sys, 'argv'):
proc_cmdline = get_proc_cmdline(as_string=False)
return os.path.basename(proc_cmdline[0])
# Get first argument that is not an CLI option
for candidate in sys.argv:
if len(candidate) > 0 and candidate[0] != '-':
basename = candidate
break
# If nothing found, fall back to executable
if basename is None:
basename = os.path.basename(sys.executable)
else:
# Assure leading paths are stripped
basename = os.path.basename(basename)
if basename == "gunicorn":
if 'setproctitle' in sys.modules:
# With the setproctitle package, gunicorn renames their processes
# to pretty things - we use those by default
# gunicorn: master [djface.wsgi]
# gunicorn: worker [djface.wsgi]
app_name = get_proc_cmdline(as_string=True)
else:
app_name = basename
elif "FLASK_APP" in os.environ:
app_name = os.environ["FLASK_APP"]
elif "DJANGO_SETTINGS_MODULE" in os.environ:
app_name = os.environ["DJANGO_SETTINGS_MODULE"].split('.')[0]
elif basename == '':
if sys.stdout.isatty():
app_name = "Interactive Console"
else:
# No arguments. Take executable as app_name
app_name = os.path.basename(sys.executable)
else:
# Last chance. app_name for "python main.py" would be "main.py" here.
app_name = basename
# We should have a good app_name by this point.
# Last conditional, if uwsgi, then wrap the name
# with the uwsgi process type
if basename == "uwsgi":
# We have an app name by this point. Now if running under
# uwsgi, augment the app name
try:
import uwsgi
if app_name == "uwsgi":
app_name = ""
else:
app_name = " [%s]" % app_name
if os.getpid() == uwsgi.masterpid():
uwsgi_type = "uWSGI master%s"
else:
uwsgi_type = "uWSGI worker%s"
app_name = uwsgi_type % app_name
except ImportError:
pass
except Exception:
logger.debug("non-fatal get_application_name: ", exc_info=True)
return app_name
def get_proc_cmdline(as_string: bool = False) -> Union[List[str], str]:
"""
Parses the process command line from the proc file system.
This function attempts to read the command line of the current process from
/proc/self/cmdline. If the proc filesystem is not available (e.g., on non-Unix
systems), it returns a default value.
Args:
as_string (bool, optional): If True, returns the command line as a single
space-separated string. If False, returns a list
of command line arguments. Defaults to False.
Returns:
Union[List[str], str]: The command line as either a list of arguments or a
space-separated string, depending on the as_string parameter.
"""
name = "python"
if os.path.isfile("/proc/self/cmdline"):
with open("/proc/self/cmdline") as cmd:
name = cmd.read()
else:
# Most likely not on a *nix based OS. Return a default
if as_string is True:
return name
else:
return [name]
# /proc/self/command line will have strings with null bytes such as "/usr/bin/python\0-s\0-d\0". This
# bit will prep the return value and drop the trailing null byte
parts = name.split('\0')
parts.pop()
if as_string is True:
parts = " ".join(parts)
return parts
def get_runtime_env_info() -> Tuple[str, str]:
"""
Returns information about the current runtime environment.
This function collects and returns details about the machine architecture
and Python version being used by the application.
Returns:
Tuple[str, str]: A tuple containing:
- Machine type (e.g., 'arm64', 'ppc64le')
- Python version string
"""
machine = platform.machine()
python_version = platform.python_version()
return machine, python_version
def log_runtime_env_info() -> None:
"""
Logs debug information about the current runtime environment.
This function retrieves machine architecture and Python version information
using get_runtime_env_info() and logs it as a debug message.
"""
machine, python_version = get_runtime_env_info()
logger.debug(f"Runtime environment: Machine: {machine}, Python version: {python_version}")
# Made with Bob