Skip to content
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

Inject into subprocesses of python #1058

Draft
wants to merge 85 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
5617f55
Update agent.instrumentation.python.deep.py
plengauer Jan 4, 2025
c24e7a4
Update agent.instrumentation.python.deep.py
plengauer Jan 4, 2025
1147e74
Update test_auto_injection_python.shell
plengauer Jan 4, 2025
c4264a7
Update test_auto_injection_python.shell
plengauer Jan 4, 2025
3d6f8ae
Update agent.instrumentation.python.deep.py
plengauer Jan 6, 2025
c85f655
Update test_auto_injection_python.shell
plengauer Jan 6, 2025
747067c
Update test_auto_injection_python.shell
plengauer Jan 6, 2025
e4bac09
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
58400e4
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
3340f04
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
52d71c4
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
9a9727b
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
f5179f7
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
f73968d
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
051eeb0
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
54098a8
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
ab8b7a8
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
588f2da
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
16409db
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
42d40f4
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
4b52c83
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
d9a7d7e
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
0c965d0
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
21aa8e9
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
9d0b095
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
7c3de10
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
6b3e275
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
1ca45cf
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
08ecc04
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
8c822da
Update agent.instrumentation.python.sh
plengauer Jan 7, 2025
d7eb502
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
03c4e11
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
5c7c1bc
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
329a82e
Update test_auto_injection_python.shell
plengauer Jan 7, 2025
3b3a9fa
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
76414ce
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
b88c968
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
8443347
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
ad975d3
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
4e02c22
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
8952c97
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
a5e7d62
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
e4900fb
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
189694d
Update agent.instrumentation.python.deep.py
plengauer Jan 7, 2025
fa6d8a8
Update test_auto_injection_python.shell
plengauer Jan 8, 2025
b027be5
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
961f0ec
Update test_auto_injection_python.shell
plengauer Jan 8, 2025
48a6c79
Update test_auto_injection_python.shell
plengauer Jan 8, 2025
25f4115
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
14326bf
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
ecf4eb0
Update test_auto_injection_python.shell
plengauer Jan 8, 2025
833c18b
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
b32865f
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
515463c
Update test_auto_injection_python.shell
plengauer Jan 8, 2025
d3b0fa2
Update test_auto_injection_python.shell
plengauer Jan 8, 2025
2543e72
Update test_auto_injection_python.shell
plengauer Jan 8, 2025
d80bbec
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
9f1fb40
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
5ee0452
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
29c415c
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
1114f5a
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
fa899bb
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
8ac2ec7
Merge branch 'main' into 709-inject-into-subprocess-of-python
plengauer Jan 8, 2025
0090bfc
Update agent.instrumentation.python.deep.py
plengauer Jan 8, 2025
9d0b84d
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
e1c4fc6
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
5285a77
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
b4d2475
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
78b11b6
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
9912091
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
332ae79
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
d8b6876
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
833ab54
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
bbd8f29
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
859f7a5
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
fc8612a
Update test_auto_injection_python.shell
plengauer Jan 9, 2025
5a56b9b
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
a29de8f
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
f8af5af
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
e6a79cf
Update agent.instrumentation.python.deep.py
plengauer Jan 9, 2025
76fa64d
Update test_auto_injection_python.shell
plengauer Jan 9, 2025
80bf900
Update agent.instrumentation.python.sh
plengauer Jan 9, 2025
a9c2856
Update agent.instrumentation.python.sh
plengauer Jan 9, 2025
97949f0
Update test_auto_injection_python.shell
plengauer Jan 9, 2025
6af92ed
Update test_auto_injection_python.shell
plengauer Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 74 additions & 41 deletions src/usr/share/opentelemetry_shell/agent.instrumentation.python.deep.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,75 @@
import sys
import os
from opentelemetry.context import attach
from opentelemetry.trace.propagation import tracecontext

traceparent = os.getenv("TRACEPARENT")
if traceparent:
propagator = tracecontext.TraceContextTextMapPropagator()
carrier = { "traceparent": traceparent }
new_context = propagator.extract(carrier=carrier)
attach(new_context)

# import subprocess
# import functools
#
# def inject_arguments(*args):
# return [ '/bin/sh', '-c', '. otel.sh\n' + args[0] + ' "$@"', 'python' ] + args[1:]
#
# TODO get current span and set traceparent and tracestate as env var
# TODO set additional env var like auto injection, ...
#
# def observed_subprocess_run(original_subprocess_run, *args, **kwargs):
# if len(args) > 0:
# args = inject_arguments(args)
# return original_subprocess_run(*args, **kwargs)
#
# def observed_subprocess_call(original_subprocess_call, *args, **kwargs):
# if len(args) > 0:
# args = inject_arguments(args)
# return original_subprocess_call(*args, **kwargs)
#
# def observed_subprocess_Popen___init__(original_subprocess_call, *args, **kwargs):
# if len(args) > 0:
# args = inject_arguments(args)
# return observed_subprocess_Popen___init__(*args, **kwargs)
#
# def instrument(observed_function, original_function):
# # functools.update_wrapper(observed_function, original_function) # TODO why do we need this?
# return functools.partial(observed_function, original_function)
#
# subprocess.run = instrument(observed_subprocess_run, subprocess.run)
# subprocess.call = instrument(observed_subprocess_call, subprocess.call)
# subprocess.Popen.__init__ = instrument(observed_subprocess_Popen___init__, subprocess.Popen.__init__)
import subprocess

try:
import opentelemetry
from opentelemetry.context import attach
from opentelemetry.trace.propagation import tracecontext

traceparent = os.getenv("TRACEPARENT")
if traceparent:
propagator = tracecontext.TraceContextTextMapPropagator()
carrier = { "traceparent": traceparent }
new_context = propagator.extract(carrier=carrier)
attach(new_context)

def inject_env(env):
if not env:
env = os.environ.copy()
carrier = {}
tracecontext.TraceContextTextMapPropagator().inject(carrier, opentelemetry.trace.set_span_in_context(opentelemetry.trace.get_current_span(), None))
if 'traceparent' in carrier:
env["OTEL_TRACEPARENT"] = carrier["traceparent"]
if 'tracestate' in carrier:
env["OTEL_TRACESTATE"] = carrier["tracestate"]
return env;

except ModuleNotFoundError:
def inject_env(env):
if not env:
env = os.environ.copy()
return env

import functools

def inject_file(file):
return '/bin/sh'

def inject_arguments(file, args):
try:
file = file.decode()
except (UnicodeDecodeError, AttributeError):
pass
if not '/' in file:
file = './' + file
if not os.path.exists(file) or not os.path.isfile(file) or not os.access(file, os.X_OK):
raise FileNotFoundError(file) # python will just trial and error all possible paths if the 'p' variants of exec are used
return [ '-c', '. otel.sh\n_otel_inject "' + str(file) + '" "$@"', 'python' ] + args

original_os_execve = os.execve
original_subprocess_Popen___init__ = subprocess.Popen.__init__

def observed_os_execv(file, args):
if type(args) is tuple:
args = list(args)
return original_os_execve(inject_file(file), [ args[0] ] + inject_arguments(file, args[1:]), inject_env(None))

def observed_os_execve(file, args, env):
if type(args) is tuple:
args = list(args)
return original_os_execve(inject_file(file), [ args[0] ] + inject_arguments(file, args[1:]), inject_env(env))

def observed_subprocess_Popen___init__(self, *args, **kwargs):
args = list(args)
if len(args) > 0 and type(args[0]) is list:
args = args[0]
print('subprocess.Popen([' + ','.join(args) + '], ' + str(kwargs) + ')', file=sys.stderr)
kwargs['env'] = inject_env(kwargs.get('env', None))
args = ([ inject_file(args[0]) ] + inject_arguments(args[0], args[1:]))
print('subprocess.Popen([' + ','.join(args) + '], ' + str(kwargs) + ')', file=sys.stderr)
return original_subprocess_Popen___init__(self, args, **kwargs);

os.execv = observed_os_execv
os.execve = observed_os_execve
subprocess.Popen.__init__ = observed_subprocess_Popen___init__
17 changes: 11 additions & 6 deletions src/usr/share/opentelemetry_shell/agent.instrumentation.python.sh
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
#!/bin/false

_otel_inject_python() {
if \[ "${OTEL_SHELL_CONFIG_INJECT_DEEP:-FALSE}" = TRUE ] && \[ -d "/opt/opentelemetry_shell/venv" ] && _otel_string_starts_with "$(\eval "$1 -V" | \cut -d ' ' -f 2)" "3." && ! _otel_string_ends_with "${2:-}" /pip && ! _otel_string_ends_with "${2:-}" /pip3; then
if \[ -d "/opt/opentelemetry_shell/venv" ] && _otel_string_starts_with "$(\eval "$1 -V" | \cut -d ' ' -f 2)" "3." && ! _otel_string_ends_with "${2:-}" /pip && ! _otel_string_ends_with "${2:-}" /pip3; then
local cmdline="$(_otel_dollar_star "$@")"
local cmdline="${cmdline#\\}"
if _otel_python_is_customize_injectable; then
local command="$1"; shift
set -- "$command" /opt/opentelemetry_shell/venv/bin/opentelemetry-instrument "${command#\\}" "$@"
if _otel_python_is_customize_injectable && \false; then
if \[ "${OTEL_SHELL_CONFIG_INJECT_DEEP:-FALSE}" = TRUE ]; then
local command="$1"; shift
set -- "$command" /opt/opentelemetry_shell/venv/bin/opentelemetry-instrument "${command#\\}" "$@"
fi
OTEL_SHELL_COMMANDLINE_OVERRIDE="$cmdline" OTEL_SHELL_COMMANDLINE_OVERRIDE_SIGNATURE="0" OTEL_SHELL_AUTO_INJECTED=TRUE PYTHONPATH=/opt/opentelemetry_shell/venv/lib/"$(\ls /opt/opentelemetry_shell/venv/lib/)"/site-packages/:"${PYTHONPATH:-}" OTEL_BSP_MAX_EXPORT_BATCH_SIZE=1 _otel_call "$@"
else
_otel_python_inject_args "$@" > /dev/null
\eval "set -- $(_otel_python_inject_args "$@")"
local command="$1"; shift
set -- "$command" /opt/opentelemetry_shell/venv/bin/opentelemetry-instrument "${command#\\}" "$@"
if \[ "${OTEL_SHELL_CONFIG_INJECT_DEEP:-FALSE}" = TRUE ]; then
local command="$1"; shift
set -- "$command" /opt/opentelemetry_shell/venv/bin/opentelemetry-instrument "${command#\\}" "$@"
fi
if \[ "${_otel_python_code_source:-}" = stdin ]; then
unset _otel_python_code_source
{ \cat /usr/share/opentelemetry_shell/agent.instrumentation.python.deep.py; \cat; } | OTEL_SHELL_COMMANDLINE_OVERRIDE="$cmdline" OTEL_SHELL_COMMANDLINE_OVERRIDE_SIGNATURE="0" OTEL_SHELL_AUTO_INJECTED=TRUE PYTHONPATH=/opt/opentelemetry_shell/venv/lib/"$(\ls /opt/opentelemetry_shell/venv/lib/)"/site-packages/:"${PYTHONPATH:-}" OTEL_BSP_MAX_EXPORT_BATCH_SIZE=1 _otel_call "$@"
Expand Down
98 changes: 97 additions & 1 deletion tests/auto/test_auto_injection_python.shell
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,103 @@ if ! which python3; then exit 0; fi

. otel.sh

#TODO test subprocesses
echo '
import os
os.execl("/bin/echo", "echo", "hello", "world", "0")
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 0"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

\echo '
import os
os.execv("/bin/echo", [ "echo", "hello", "world", "1" ])
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 1"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

echo '
import os
os.spawnl(os.P_WAIT, "/bin/echo", "echo", "hello", "world", "2")
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 2"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

echo '
import os
os.spawnlp(os.P_WAIT, "echo", "echo", "hello", "world", "3")
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 3"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

echo '
import os
os.spawnv(os.P_WAIT, "/bin/echo", ["echo", "hello", "world", "4"])
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 4"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

echo '
import os
os.spawnvp(os.P_WAIT, "echo", ["echo", "hello", "world", "5"])
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 5"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

printf '%s' '
import subprocess
with subprocess.Popen(["/usr/bin/echo", "hello", "world", "6"], stdout=subprocess.DEVNULL) as proc:
pass
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 6"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

# dir=$(mktemp -d)
# python3 -m venv --system-site-packages "$dir"/venv || exit 1
# . "$dir"/venv/bin/activate
# pip3 install opentelemetry-distro opentelemetry-exporter-otlp
# opentelemetry-bootstrap --action install || exit 1
printf '%s' '
import subprocess
with subprocess.Popen(["/usr/bin/echo", "hello", "world", "7"], stdout=subprocess.DEVNULL) as proc:
pass
' | python3
# deactivate
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 7"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

echo '
import subprocess
subprocess.run(["/usr/bin/echo", "hello", "world", "8"])
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 8"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

echo '
import subprocess
subprocess.run("echo hello world 9", shell=True)
' | python3
assert_equals 0 $?
span="$(resolve_span '.name == "echo hello world 9"')"
assert_equals "SpanKind.INTERNAL" $(\echo "$span" | \jq -r '.kind')
assert_not_equals null $(\echo "$span" | \jq -r '.parent_id')

export OTEL_SHELL_CONFIG_INJECT_DEEP=TRUE

Expand Down