blob: bf24ba1bcca96084c7634664be2ca308d4337fe1 [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" This version of sitecustomize will
1. initialize the SkyWalking Python Agent.
2. invoke an existing sitecustomize.py.
Not compatible with Python <= 3.3
When executing commands with `sw-python run command`
This particular sitecustomize module will be picked up by any valid replacement
process(command) invoked by os.execvp in runner.py, thus will be seen by potentially
incompatible Python versions and system interpreters, which certainly will cause problems.
Therefore, an attempt to use command leading to a different Python interpreter should be stopped.
"""
import importlib
import logging
import os
import platform
import sys
def _get_sw_loader_logger():
""" Setup a new logger with passed skywalking CLI env vars,
don't import from skywalking, it may not be on sys.path
if user misuses the CLI to run programs out of scope
"""
from logging import getLogger
logger = getLogger('skywalking-loader')
ch = logging.StreamHandler()
formatter = logging.Formatter('%(name)s [%(threadName)s] [%(levelname)s] %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
logger.propagate = False
if os.environ.get('SW_PYTHON_CLI_DEBUG_ENABLED') == 'True': # set from the original CLI runner
logger.setLevel(level=logging.DEBUG)
return logger
_sw_loader_logger = _get_sw_loader_logger()
_sw_loader_logger_debug_enabled = _sw_loader_logger.isEnabledFor(logging.DEBUG)
# DEBUG messages in case execution goes wrong
if _sw_loader_logger_debug_enabled:
_sw_loader_logger.debug('---------------sitecustomize.py---------------')
_sw_loader_logger.debug(f'Successfully imported sitecustomize.py from `{__file__}`')
_sw_loader_logger.debug(f'You are inside working dir - {os.getcwd()}')
_sw_loader_logger.debug(f'Using Python version - {sys.version} ')
_sw_loader_logger.debug(f'Using executable at - {sys.executable}')
_sw_loader_logger.debug(f'System Base Python executable location {sys.base_prefix}')
if sys.prefix != sys.base_prefix:
_sw_loader_logger.debug('[The SkyWalking agent bootstrapper is running inside a virtual environment]')
# It is possible that someone else also has a sitecustomize.py
# in sys.path either set by .pth files or manually, we need to run them as well
# The path to user sitecustomize.py is after ours in sys.path, needs to be imported here.
# just to be safe, remove loader thoroughly from sys.path and also sys.modules
cleared_path = [p for p in sys.path if p != os.path.dirname(__file__)]
sys.path = cleared_path # remove this version from path
loaded = sys.modules.pop('sitecustomize', None) # pop sitecustomize from loaded
# now try to find the original sitecustomize provided in user env
try:
loaded = importlib.import_module('sitecustomize')
_sw_loader_logger.debug(f'Found user sitecustomize file {loaded}, imported')
except ImportError: # ModuleNotFoundError
_sw_loader_logger.debug('Original sitecustomize module not found, skipping.')
finally: # surprise the import error by adding loaded back
sys.modules['sitecustomize'] = loaded
# This sitecustomize by default doesn't remove the loader dir from PYTHONPATH,
# Thus, subprocesses and multiprocessing also inherits this sitecustomize.py
# This behavior can be turned off using a user provided env below
# os.environ['SW_PYTHON_BOOTSTRAP_PROPAGATE']
if os.environ.get('SW_AGENT_SW_PYTHON_BOOTSTRAP_PROPAGATE') == 'False':
if os.environ.get('PYTHONPATH'):
partitioned = os.environ['PYTHONPATH'].split(os.path.pathsep)
loader_path = os.path.dirname(__file__)
if loader_path in partitioned: # check if we are already removed by a third-party
partitioned.remove(loader_path)
os.environ['PYTHONPATH'] = os.path.pathsep.join(partitioned)
_sw_loader_logger.debug('Removed loader from PYTHONPATH, spawned process will not have agent enabled')
# Note that users could be misusing the CLI to call a Python program that
# their Python env doesn't have SkyWalking installed. Or even call another
# env that has another version of SkyWalking installed, which is leads to problems.
# Even if `import skywalking` was successful, doesn't mean its the same one where sw-python CLI was run.
# Needs further checking for Python versions and prefix that passed down from CLI in os.environ.
cli_python_version = os.environ.get('SW_PYTHON_VERSION')
cli_python_prefix = os.environ.get('SW_PYTHON_PREFIX')
version_match = cli_python_version == platform.python_version()
# windows can sometimes make capitalized path, lower them can help
prefix_match = cli_python_prefix.lower() == os.path.realpath(os.path.normpath(sys.prefix)).lower()
if not (version_match and prefix_match):
_sw_loader_logger.error(
f'\nPython used by sw-python CLI - v{cli_python_version} at {cli_python_prefix}\n'
f'Python used by your actual program - v{platform.python_version()} '
f'at {os.path.realpath(os.path.normpath(sys.prefix))}'
)
_sw_loader_logger.error('The sw-python CLI was instructed to run a program '
'using an different Python installation '
'this is not safe and loader will not proceed. '
'Please make sure that sw-python cli, skywalking agent and your '
'application are using the same Python installation, '
'Rerun with debug flag, `sw-python -d run yourapp` for some troubleshooting information.'
'use `which sw-python` to find out the invoked CLI location')
os._exit(1) # do not go further
else:
from skywalking import agent, config
# also override debug for skywalking agent itself
if os.environ.get('SW_PYTHON_CLI_DEBUG_ENABLED') == 'True': # set from the original CLI runner
config.agent_logging_level = 'DEBUG'
# Currently supports configs read from os.environ
# This is for python 3.6 - 3.7(maybe) argv is not set for embedded interpreter thus will cause failure in
# those libs that imports argv from sys, we need to set it manually if it's not there already
# otherwise the plugin install will fail and things won't work properly, example - Sanic
if not hasattr(sys, 'argv'):
sys.argv = ['']
# noinspection PyBroadException
try:
_sw_loader_logger.debug('SkyWalking Python Agent starting, loader finished.')
agent.start()
except Exception:
_sw_loader_logger.exception('SkyWalking Python Agent failed to start, please inspect your package installation')