Skip to content
Merged
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Library | Plugin Name
| [urllib.request](https://docs.python.org/3/library/urllib.request.html) | `sw_urllib_request` |
| [requests](https://requests.readthedocs.io/en/master/) | `sw_requests` |
| [Flask](https://flask.palletsprojects.com/en/1.1.x/) | `sw_flask` |
| [PyMySQL](https://pymysql.readthedocs.io/en/latest/) | `sw_pymysql` |

## API

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
extras_require={
"test": [
"testcontainers",
"Werkzeug"
"Werkzeug",
"pymysql",
],
},
classifiers=[
Expand Down
1 change: 1 addition & 0 deletions skywalking/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Component(Enum):
General = 7000 # built-in modules that may not have a logo to display
Flask = 7001
Requests = 7002
PyMysql = 7003


class Layer(Enum):
Expand Down
57 changes: 57 additions & 0 deletions skywalking/plugins/sw_pymysql/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#
# 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.
#
import logging

from skywalking import Layer, Component
from skywalking.trace import tags
from skywalking.trace.carrier import Carrier
from skywalking.trace.context import get_context
from skywalking.trace.tags import Tag

logger = logging.getLogger(__name__)


def install():
# noinspection PyBroadException
try:
from pymysql.cursors import Cursor

_execute = Cursor.execute

def _sw_execute(this: Cursor, query, args=None):
Comment thread
kezhenxu94 marked this conversation as resolved.
peer = "%s:%s" % (this.connection.host, this.connection.port)

context = get_context()
carrier = Carrier()
with context.new_exit_span(op="Mysql/PyMsql/execute", peer=peer, carrier=carrier) as span:
span.layer = Layer.Database
span.component = Component.PyMysql
try:
res = _execute(this, query, args)

span.tag(Tag(key=tags.DbType, val="mysql"))
span.tag(Tag(key=tags.DbInstance, val=this.connection.db.decode("utf-8")))
span.tag(Tag(key=tags.DbStatement, val=query))

except BaseException as e:
span.raised()
raise e
return res

Cursor.execute = _sw_execute
except Exception:
logger.warning('failed to install plugin %s', __name__)
3 changes: 3 additions & 0 deletions skywalking/trace/tags/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@
HttpUrl = 'url'
HttpMethod = 'http.method'
HttpStatus = 'status.code'
DbType = 'db.type'
DbInstance = 'db.instance'
DbStatement = 'db.statement'
16 changes: 16 additions & 0 deletions tests/plugin/sw_pymysql/__init__.py
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.
#
78 changes: 78 additions & 0 deletions tests/plugin/sw_pymysql/docker-compose.yml
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.
#

version: '2.1'

services:
collector:
extends:
service: collector
file: ../docker/docker-compose.base.yml

mysql:
image: mysql:5.7
hostname: mysql
ports:
- 3306:3306
- 33060:33060
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=test
healthcheck:
test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306"]
interval: 5s
timeout: 60s
retries: 120
networks:
- beyond

provider:
extends:
service: agent
file: ../docker/docker-compose.base.yml
ports:
- 9091:9091
volumes:
- ./services/provider.py:/app/provider.py
command: ['bash', '-c', 'pip install flask && pip install PyMySQL && python3 /app/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

consumer:
extends:
service: agent
file: ../docker/docker-compose.base.yml
ports:
- 9090:9090
volumes:
- ./services/consumer.py:/app/consumer.py
command: ['bash', '-c', 'pip install flask && python3 /app/consumer.py']
depends_on:
collector:
condition: service_healthy
provider:
condition: service_healthy

networks:
beyond:
109 changes: 109 additions & 0 deletions tests/plugin/sw_pymysql/expected.data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#
# 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.
#

segmentItems:
- serviceName: provider
segmentSize: 1
segments:
- segmentId: not null
spans:
- operationName: Mysql/PyMsql/execute
operationId: 0
parentSpanId: 0
spanId: 1
spanLayer: Database
tags:
- key: db.type
value: mysql
- key: db.instance
value: test
- key: db.statement
value: select 1
startTime: gt 0
endTime: gt 0
componentId: 7003
spanType: Exit
peer: mysql:3306
skipAnalysis: false
- operationName: /users
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
tags:
- key: http.method
value: POST
- key: url
value: http://provider:9091/users
- key: status.code
value: '200'
refs:
- parentEndpoint: /users
networkAddress: provider:9091
refType: CrossProcess
parentSpanId: 1
parentTraceSegmentId: not null
parentServiceInstance: not null
parentService: consumer
traceId: not null
startTime: gt 0
endTime: gt 0
componentId: 7001
spanType: Entry
peer: not null
skipAnalysis: false
- serviceName: consumer
segmentSize: 1
segments:
- segmentId: not null
spans:
- operationName: /users
operationId: 0
parentSpanId: 0
spanId: 1
spanLayer: Http
tags:
- key: http.method
value: POST
- key: url
value: http://provider:9091/users
- key: status.code
value: '200'
startTime: gt 0
endTime: gt 0
componentId: 7002
spanType: Exit
peer: provider:9091
skipAnalysis: false
- operationName: /users
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
tags:
- key: http.method
value: GET
- key: url
value: http://0.0.0.0:9090/users
- key: status.code
value: '200'
startTime: gt 0
endTime: gt 0
componentId: 7001
spanType: Entry
peer: not null
skipAnalysis: false
16 changes: 16 additions & 0 deletions tests/plugin/sw_pymysql/services/__init__.py
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.
#
37 changes: 37 additions & 0 deletions tests/plugin/sw_pymysql/services/consumer.py
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.
#

import requests

from skywalking import agent, config

if __name__ == '__main__':
config.service_name = 'consumer'
config.logging_level = 'DEBUG'
agent.start()

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/users", methods=["POST", "GET"])
def application():
res = requests.post("http://provider:9091/users")
return jsonify(res.json())

PORT = 9090
app.run(host='0.0.0.0', port=PORT, debug=True)
45 changes: 45 additions & 0 deletions tests/plugin/sw_pymysql/services/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# 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.
#

import time

from skywalking import agent, config

if __name__ == '__main__':
config.service_name = 'provider'
config.logging_level = 'DEBUG'
agent.start()

from flask import Flask, jsonify
import pymysql.cursors
Comment thread
kezhenxu94 marked this conversation as resolved.

app = Flask(__name__)

@app.route("/users", methods=["POST", "GET"])
def application():
time.sleep(0.5)
connection = pymysql.connect(host='mysql', user='root', password='root', db='test', charset='utf8mb4')
with connection.cursor() as cursor:
sql = "select 1"
cursor.execute(sql)

connection.close()

return jsonify({"song": "Despacito", "artist": "Luis Fonsi"})

PORT = 9091
app.run(host='0.0.0.0', port=PORT, debug=True)
Loading