-
Notifications
You must be signed in to change notification settings - Fork 108
Support loading Python agent through sw-python CLI #156
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
Changes from 5 commits
00b3210
662cb09
5d8af64
a296090
b92a954
a9e22b2
71f2931
97257ba
4e1bdef
5ca9537
3a2a651
9d09113
35ec28d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # | ||
| # 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. | ||
| # | ||
|
|
||
| """ Just an entry point script | ||
| python -m cli run command | ||
| or just use the setup console script | ||
| sw-python run command after setup install | ||
| """ | ||
| from skywalking.bootstrap.cli import sw_python | ||
|
|
||
| if __name__ == '__main__': | ||
| sw_python.start() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # SkyWalking Python Agent Command-Line Interface(CLI) | ||
|
|
||
| You would at least need to add the following lines to your applications in previous releases to get the agent attached and running. | ||
Superskyyy marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```python | ||
| from skywalking import agent | ||
| agent.start() | ||
| ``` | ||
|
|
||
|
|
||
| Now the SkyWalking Python agent implements a command-line interface that can be utilized to attach the agent to your | ||
| awesome applications during deployment **without changing any application code**, | ||
| just like the [SkyWalking Java Agent](https://github.com/apache/skywalking). | ||
|
|
||
| ## Usage | ||
|
|
||
| Upon successful installation of the SkyWalking Python agent via pip, | ||
Superskyyy marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| a command-line script `sw-python` is installed in your environment(virtual env preferred). | ||
|
|
||
| ### The run option | ||
Superskyyy marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Currently, the `sw-python` CLI provides a `run` option, which you can use to execute your applications | ||
| (either begins with the `python` command or Python-based programs like `gunicorn` on your path) | ||
| just like you invoke them normally, plus a prefix, the following example demonstrates the usage. | ||
|
|
||
| If your previous command to run your gunicorn application is: | ||
|
|
||
| `gunicorn app.wsgi` | ||
|
|
||
| Please change it to: | ||
|
|
||
| `sw-python run gunicorn app.wsgi` | ||
|
|
||
| The SkyWalking Python agent will startup along with your application shortly. | ||
|
|
||
| Note that the command does work with multiprocessing and subprocess as long as the `PYTHONPATH` is inherited, | ||
| please configure the environment variables configuration accordingly based on the general documentation. | ||
|
|
||
| When executing commands with `sw-python run command`, your command's Python interpreter will pick up the SkyWalking loader module. | ||
|
|
||
| It is not safe to attach SkyWalking Agent to those commands that resides in another Python installation | ||
| because incompatible Python versions and mismatched SkyWalking versions can cause problems. | ||
| Therefore, any attempt to pass a command that uses a different Python interpreter/ environment will not bring up | ||
| SkyWalking Python Agent even if another SkyWalking Python agent is installed there(no matter the version), | ||
| and will force exit with an error message indicating the reasoning. | ||
|
|
||
| #### Disabling child processes from starting new agents | ||
|
|
||
| Sometimes you don't actually need the agent to monitor anything in a child process. | ||
|
|
||
| If you do not need the agent to get loaded for application child processes, you can turn off the behavior by setting an environment variable. | ||
|
|
||
| `SW_PYTHON_BOOTSTRAP_PROPAGATE` to `False` | ||
|
|
||
| Note the auto bootstrap depends on the environment inherited by child processes, | ||
| thus prepending a new sitecustomize path to or removing the loader path from the `PYTHONPATH` could prevent the agent from loading in a child process. | ||
|
|
||
| ### Configuring the agent | ||
|
|
||
| You would normally want to provide additional configurations other than the default ones. | ||
|
|
||
| #### Through environment variables | ||
|
|
||
| The currently supported method is to provide the environment variables listed | ||
| in [EnvVars Doc](EnvVars.md) as instructed in the [README](../README.md). | ||
|
|
||
| #### Through a sw-config.yaml | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing we should note when implementing this is that we've been trying to minimize our dependencies as an agent, so we'd better choose a reasonable file format that Python built-in libs can parse, instead of bringing another library, to avoid version conflicts with user applications, |
||
|
|
||
| Currently, only environment variable configuration is supported; an optional `yaml` configuration is to be implemented. | ||
|
|
||
| ### Enabling CLI DEBUG mode | ||
|
|
||
| Note the CLI is a new feature that manipulates the Python interpreter bootstrap behaviour, there could be unsupported cases. | ||
|
|
||
| If you encounter unexpected problems, please turn on the DEBUG mode by adding the `-d` or `--debug` flag to your `sw-python` command, as shown below. | ||
|
|
||
| From: `sw-python run command` | ||
|
|
||
| To: `sw-python -d run command` | ||
|
|
||
| Please attach the debug logs to the [SkyWalking Issues](https://github.com/apache/skywalking/issues) section if you believe it is a bug, | ||
| [idea discussions](https://github.com/apache/skywalking/discussions) and [pull requests](https://github.com/apache/skywalking-python/pulls) are always welcomed. | ||
|
|
||
| #### Known limitations | ||
|
|
||
| 1. The CLI may not work properly with arguments that involve double quotation marks in some shells. | ||
| 2. The CLI and bootstrapper stdout logs could get messy in Windows shells. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # | ||
| # 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 sub-package is for the convenience of deployment and automation | ||
| A CLI for running Python scripts and programs with SkyWalking Python Agent automatically attached. | ||
| `loader/sitecustomize.py` is invoked by the Python interpreter at startup. | ||
| """ | ||
|
|
||
| __version__ = '0.6.0' | ||
Superskyyy marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| import logging | ||
|
|
||
|
|
||
| def get_cli_logger(): | ||
| """ A logger used by sw-python CLI """ | ||
| logger = logging.getLogger('skywalking-cli') | ||
| ch = logging.StreamHandler() | ||
| formatter = logging.Formatter('%(name)s [%(levelname)s] %(message)s') | ||
| ch.setFormatter(formatter) | ||
| logger.addHandler(ch) | ||
| logger.propagate = False | ||
|
|
||
| return logger | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # | ||
| # 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. | ||
| # | ||
|
|
||
|
|
||
| class SWRunnerFailure(Exception): | ||
| """ Exception runner fails to execute given user command """ | ||
| pass |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # | ||
| # 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 module is installed during package setup """ | ||
| import argparse | ||
| import logging | ||
|
|
||
| from skywalking.bootstrap import __version__, get_cli_logger | ||
| from skywalking.bootstrap.cli import SWRunnerFailure | ||
| from skywalking.bootstrap.cli.utility import runner | ||
|
|
||
| _options = { | ||
| 'run': runner, | ||
| } | ||
|
|
||
| cli_logger = get_cli_logger() | ||
|
|
||
|
|
||
| def start() -> None: | ||
| """ Entry point of CLI """ | ||
| parser = argparse.ArgumentParser(description='SkyWalking Python Agent CLI', | ||
| epilog='Append your command, with SkyWalking agent attached for you automatically', | ||
| allow_abbrev=False) | ||
|
|
||
| parser.add_argument('option', help='CLI options, now only supports `run`, for help please type `sw-python -h` ' | ||
| 'or refer to the CLI documentation', | ||
| choices=list(_options.keys())) | ||
|
|
||
| # TODO support parsing optional sw_config.yaml | ||
| # parser.add_argument('-config', nargs='?', type=argparse.FileType('r'), | ||
| # help='Optionally takes a sw_python.yaml config file') | ||
|
|
||
| # The command arg compress all remaining args into itself | ||
| parser.add_argument('command', help='Your original commands e.g. gunicorn app.wsgi', | ||
| nargs=argparse.REMAINDER, metavar='command') | ||
|
|
||
| parser.add_argument('-d', '--debug', help='Print CLI debug logs to stdout', action='store_true') | ||
|
|
||
| # To handle cases with flags and positional args in user commands | ||
| args = parser.parse_args() # type: argparse.Namespace | ||
|
|
||
| cli_logger.setLevel(logging.DEBUG if args.debug else logging.INFO) | ||
|
|
||
| cli_logger.debug("Args received {}".format(args)) | ||
|
|
||
| if not args.command: | ||
| cli_logger.error("Command is not provided, please type `sw-python -h` for the list of command line arguments") | ||
| return | ||
| try: | ||
| dispatch(args) | ||
| except SWRunnerFailure: | ||
| cli_logger.debug('Failed to run the given user application command `{}`'.format(' '.join(args.command))) | ||
| pass | ||
|
|
||
|
|
||
| def dispatch(args: argparse.Namespace) -> None: | ||
| """ Dispatches parsed args to a worker """ | ||
| cli_command, actual_command = args.option, args.command | ||
|
|
||
| cli_logger.debug("SkyWalking Python agent version {} with CLI command '{}' and argument {}".format | ||
| (__version__, cli_command, actual_command)) | ||
|
|
||
| # Dispatch actual user application command to runner | ||
| _options[cli_command].execute(actual_command) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # | ||
| # 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. | ||
| # |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # | ||
| # 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. | ||
| # | ||
|
|
||
| """ User application command runner """ | ||
| import logging | ||
| import os | ||
| import platform | ||
| import sys | ||
| from typing import List | ||
|
|
||
| from skywalking.bootstrap import get_cli_logger | ||
|
|
||
| cli_logger = get_cli_logger() | ||
|
|
||
|
|
||
| def execute(command: List[str]) -> None: | ||
| """ Set up environ and invokes the given command to replace current process """ | ||
|
|
||
| cli_logger.debug("SkyWalking Python agent `runner` received command {}".format(command)) | ||
|
|
||
| cli_logger.debug("Adding sitecustomize.py to PYTHONPATH") | ||
|
|
||
| from skywalking.bootstrap.loader import __file__ as loader_dir | ||
|
|
||
| loader_path = os.path.dirname(loader_dir) | ||
| new_path = "" | ||
|
|
||
| python_path = os.environ.get('PYTHONPATH') | ||
| if python_path: # If there is already a different PYTHONPATH, PREPEND to it as we must get loaded first. | ||
| partitioned = python_path.split(os.path.pathsep) | ||
| if loader_path not in partitioned: # check if we are already there | ||
| new_path = os.path.pathsep.join([loader_path, python_path]) # type: str | ||
kezhenxu94 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # When constructing sys.path PYTHONPATH is always | ||
| # before other paths and after interpreter invoker path, which is here or none | ||
| os.environ['PYTHONPATH'] = new_path if new_path else loader_path | ||
| cli_logger.debug("Updated PYTHONPATH - {}".format(os.environ['PYTHONPATH'])) | ||
|
|
||
| # Used in sitecustomize to compare command's Python installation with CLI | ||
| # If not match, need to stop agent from loading, and kill the process | ||
| os.environ['SW_PYTHON_PREFIX'] = os.path.realpath(os.path.normpath(sys.prefix)) | ||
| os.environ['SW_PYTHON_VERSION'] = platform.python_version() | ||
|
|
||
| # Pass down the logger debug setting to the replaced process, need a new logger there | ||
| os.environ['SW_PYTHON_CLI_DEBUG_ENABLED'] = 'True' if cli_logger.level == logging.DEBUG else 'False' | ||
|
|
||
| try: | ||
| cli_logger.debug('New process starting with file - `{}` args - `{}`'.format(command[0], command)) | ||
| os.execvp(command[0], command) | ||
| except OSError: | ||
| raise | ||
Uh oh!
There was an error while loading. Please reload this page.