new-plugin
Scaffold a new SkyWalking Python instrumentation plugin with all required files (plugin code, tests, docker-compose, expected data, services)
معلومات المصدر
- المستودع
- apache/skywalking-python
- آخر نشاط في المصدر
- ١٢ أبريل ٢٠٢٦ في ١١:٥٩
- لغة SKILL.md المكتشفة
- الإنجليزية
- النجوم
- ٢١٩
- التفرعات
- ١١٣
خيارات التثبيت
يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.
مراجعة ملفات المصدر
اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.
عرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط- 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:
عرض على GitHubملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub