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)

Informações da origem

Repositório
apache/skywalking-python
Última atividade na origem
12 de abril de 2026 às 11:59
Idioma detectado do SKILL.md
inglês
Estrelas
219
Forks
113

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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:
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub