Skip to main content

new-plugin

Scaffold a new SkyWalking Python instrumentation plugin with all required files (plugin code, tests, docker-compose, expected data, services)

Informations de source

Dépôt
apache/skywalking-python
Dernière activité de la source
12 avril 2026 à 11:59
Langue détectée de SKILL.md
anglais
Étoiles
219
Forks
113

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
new-plugin
description
Scaffold a new SkyWalking Python instrumentation plugin with all required files (plugin code, tests, docker-compose, expected data, services)
user-invocable
true
# New SkyWalking Python Plugin Generate a complete instrumentation plugin for the SkyWalking Python agent. Ask the user for: 1. **Library name** (e.g., `httpx`, `pymemcache`, `clickhouse-driver`) 2. **Plugin type**: web framework (Entry spans), HTTP client (Exit spans), database (Exit spans), cache (Exit spans), message queue (Entry+Exit spans), RPC (Entry+Exit spans) 3. **Library versions to test** (e.g., `['1.0', '2.0']`) 4. **Minimum Python version** (default `>=3.7`) 5. **External service needed for tests?** (e.g., Redis, MySQL, Kafka — or none for HTTP client plugins) If the user provides a library name without details, research the library to determine the appropriate type and instrumentation points. ## Files to Generate ### 1. Plugin Module: `skywalking/plugins/sw_<name>.py` Use the Apache 2.0 license header (copy from any existing plugin file). Follow these patterns based on plugin type: **For HTTP Exit plugins** (like `sw_requests.py`): ```python from skywalking import Layer, Component, config from skywalking.trace.context import get_context, NoopContext from skywalking.trace.span import NoopSpan from skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpStatusCode link_vector = ['<documentation URL>'] support_matrix = { '<pip-package-name>': { '>=3.13': ['<major>.*'], # use .* wildcard for latest patch (e.g., '4.*') '>=3.10': ['<older_minor>.*', '<major>.*'], } } note = """""" def install(): from <library> import <Class> _original = <Class>.<method> def _sw_method(this, *args, **kwargs): # Parse URL/peer from args span = NoopSpan(NoopContext()) if config.ignore_http_method_check(method) \ else get_context().new_exit_span(op=path, peer=netloc, component=Component.<Name>) with span: carrier = span.inject() span.layer = Layer.Http # Inject carrier into outgoing headers for item in carrier: headers[item.key] = item.val span.tag(TagHttpMethod(method)) span.tag(TagHttpURL(url)) res = _original(this, *args, **kwargs) span.tag(TagHttpStatusCode(res.status_code)) if res.status_code >= 400: span.error_occurred = True return res <Class>.<method> = _sw_method ``` **For Web Entry plugins** (like `sw_flask.py`): ```python from skywalking import Layer, Component, config from skywalking.trace.carrier import Carrier from skywalking.trace.context import get_context, NoopContext from skywalking.trace.span import NoopSpan from skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpStatusCode def install(): from <framework> import <App> _original = <App>.<handler_method> def _sw_handler(this, *args, **kwargs): req = <get_request> carrier = Carrier() for item in carrier: if item.key.capitalize() in req.headers: item.val = req.headers[item.key.capitalize()] span = NoopSpan(NoopContext()) if config.ignore_http_method_check(req.method) \ else get_context().new_entry_span(op=req.path, carrier=carrier, inherit=Component.General) with span: span.layer = Layer.Http span.component = Component.<Name> span.peer = f"{remote_addr}:{remote_port}" span.tag(TagHttpMethod(req.method)) span.tag(TagHttpURL(req.url)) resp = _original(this, *args, **kwargs) span.tag(TagHttpStatusCode(resp.status_code)) if resp.status_code >= 400: span.error_occurred = True return resp <App>.<handler_method> = _sw_handler ``` **For Database/Cache Exit plugins** (like `sw_redis.py`): ```python from skywalking import Layer, Component from skywalking.trace.context import get_context from skywalking.trace.tags import TagDbType, TagDbInstance, TagDbStatement # or TagCacheType, TagCacheOp, etc. def install(): from <library> import <Connection> _original = <Connection>.<method> def _sw_method(this, *args, **kwargs): peer = f'{this.host}:{this.port}' with get_context().new_exit_span(op='<DB>/<operation>', peer=peer, component=Component.<Name>) as span: span.layer = Layer.Database # or Layer.Cache span.tag(TagDbType('<DB type>')) span.tag(TagDbStatement(query)) res = _original(this, *args, **kwargs) return res <Connection>.<method> = _sw_method ``` **For MQ plugins** (producer Exit + consumer Entry): - Producer: new_exit_span, inject carrier into message headers, Layer.MQ - Consumer: new_entry_span, extract carrier from message headers, Layer.MQ **For async plugins**: Use `async def` wrappers, `await` the original call. **For C extension libraries**: Use `wrapt.ObjectProxy` pattern (see `sw_psycopg2.py`). ### 2. Component Enum: `skywalking/__init__.py` Add a new entry to the `Component` enum. Check the last Python-specific ID (7000+) and increment. Example: ```python NewLib = 7020 # Next available ID ``` If the component already exists in the SkyWalking ecosystem (check existing enum), reuse that ID. ### 3. Test Directory: `tests/plugin/{data|http|web}/sw_<name>/` Choose the subdirectory: - `web/` for web frameworks - `http/` for HTTP clients - `data/` for databases, caches, message queues Create these files: #### `__init__.py` Empty file with license header. #### `test_<name>.py` ```python from typing import Callable import pytest import requests from skywalking.plugins.sw_<name> import support_matrix from tests.orchestrator import get_test_vector from tests.plugin.base import TestPluginBase @pytest.fixture def prepare(): return lambda *_: requests.get('http://0.0.0.0:9090/<endpoint>', timeout=5) class TestPlugin(TestPluginBase): @pytest.mark.parametrize('version', get_test_vector(lib_name='<name>', support_matrix=support_matrix)) def test_plugin(self, docker_compose, version): self.validate() ``` #### `docker-compose.yml` ```yaml version: '2.1' services: collector: extends: service: collector file: ../../docker-compose.base.yml # Add external service if needed (database, cache, MQ broker) # <service_name>: # image: <image> # ports: [<port>:<port>] # healthcheck: ... # networks: [beyond] provider: extends: service: agent file: ../../docker-compose.base.yml ports: - 9091:9091 volumes: - .:/app command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && sw-python run python3 /app/services/provider.py'] depends_on: collector: condition: service_healthy healthcheck: test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091"] interval: 5s timeout: 60s retries: 120 environment: SW_AGENT_NAME: provider SW_AGENT_LOGGING_LEVEL: DEBUG consumer: extends: service: agent file: ../../docker-compose.base.yml ports: - 9090:9090 volumes: - .:/app command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && sw-python run python3 /app/services/consumer.py'] depends_on: collector: condition: service_healthy provider: condition: service_healthy environment: SW_AGENT_NAME: consumer SW_AGENT_LOGGING_LEVEL: DEBUG networks: beyond: ``` Key points: - Consumer listens on port 9090, provider on 9091 - Consumer calls provider to create cross-process trace - Use `sw-python run python3` to start with agent instrumentation - The consumer uses Flask to expose an HTTP endpoint for the test's `prepare()` fixture to hit - The provider uses the target library (e.g., makes a Redis call, DB query) - Both install the target library via `pip install -r /app/requirements.txt` #### `services/provider.py` Flask app on port 9091 that uses the target library. Example for a data plugin: ```python if __name__ == '__main__': from flask import Flask, jsonify app = Flask(__name__) @app.route('/endpoint', methods=['POST', 'GET']) def handler(): import <target_library> # Make a call using the target library # e.g., client = redis.StrictRedis(host='redis'); client.get('key') return jsonify({'status': 'ok'}) app.run(host='0.0.0.0', port=9091) ``` #### `services/consumer.py` Flask app on port 9090 that calls the provider: ```python import requests if __name__ == '__main__': from flask import Flask, jsonify app = Flask(__name__) @app.route('/endpoint', methods=['POST', 'GET']) def handler(): res = requests.post('http://provider:9091/endpoint', timeout=5) return jsonify(res.json()) app.run(host='0.0.0.0', port=9090) ``` #### `services/__init__.py` Empty file with license header. #### `expected.data.yml` Define expected spans. Structure depends on plugin type: For a **data/cache Exit** plugin (consumer -> provider -> external service): ```yaml segmentItems: - serviceName: provider segmentSize: 1 segments: - segmentId: not null spans: - operationName: <DB>/<operation> parentSpanId: 0 spanId: 1 spanLayer: Database # or Cache startTime: gt 0 endTime: gt 0 componentId: <ID> spanType: Exit peer: <service>:<port> skipAnalysis: false tags: - key: db.type value: <type> - key: db.statement value: <query> - operationName: /endpoint parentSpanId: -1 spanId: 0 spanLayer: Http startTime: gt 0 endTime: gt 0 componentId: 7001 spanType: Entry peer: not null skipAnalysis: false tags: - key: http.method value: POST - key: http.url value: http://provider:9091/endpoint - key: http.status_code value: '200' refs: - parentEndpoint: /endpoint networkAddress: 'provider:9091' refType: CrossProcess parentSpanId: 1 parentTraceSegmentId: not null parentServiceInstance: not null parentService: consumer traceId: not null - serviceName: consumer segmentSize: 1 segments: - segmentId: not null spans: - operationName: /endpoint parentSpanId: 0 spanId: 1 spanLayer: Http startTime: gt 0 endTime: gt 0 componentId: 7002 spanType: Exit peer: provider:9091 skipAnalysis: false tags: - key: http.method value: POST - key: http.url value: http://provider:9091/endpoint - key: http.status_code value: '200' - operationName: /endpoint parentSpanId: -1 spanId: 0 spanLayer: Http startTime: gt 0 endTime: gt 0 componentId: 7001 spanType: Entry peer: not null skipAnalysis: false tags: - key: http.method value: GET - key: http.url value: http://0.0.0.0:9090/endpoint - key: http.status_code value: '200' ``` Important notes for expected data: - Spans within a segment are ordered **child-first** (highest spanId first) - Entry spans have `parentSpanId: -1` - Exit spans reference the entry span as parent - Cross-process refs link consumer exit span to provider entry span - `componentId` must match the Component enum value exactly - Consumer's exit span uses Requests component (7002) since it calls via `requests.post()` - Consumer's entry span uses Flask component (7001) since it receives via Flask ### 4. Update `pyproject.toml` Add the library to `[tool.poetry.group.plugins.dependencies]`: ```bash poetry add <library> --group plugins ``` ### 5. Regenerate Docs ```bash make doc-gen ``` ### 6. Lint Check ```bash make lint ``` ## Post-Generation Reminders After generating all files, remind the user:
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub