Skip to content

Commit a59a8dd

Browse files
[Plugin] Add plugin for urllib3 library (#69)
Co-authored-by: kezhenxu94 <[email protected]>
1 parent c733985 commit a59a8dd

File tree

10 files changed

+370
-0
lines changed

10 files changed

+370
-0
lines changed

docs/Plugins.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ Library | Versions | Plugin Name
1414
| [pika](https://pika.readthedocs.io/en/stable/) | 1.1.0 | `sw_rabbitmq` |
1515
| [pymongo](https://pymongo.readthedocs.io/en/stable/) | 3.11.0 | `sw_pymongo` |
1616
| [elasticsearch](https://github.com/elastic/elasticsearch-py) | 7.9.0 | `sw_elasticsearch` |
17+
| [urllib3](https://urllib3.readthedocs.io/en/latest/) | >= 1.25.9 <= 1.25.10 | `sw_urllib3` |
1718

1819
The column `Versions` only indicates that the versions are tested, if you found the newer versions are also supported, welcome to add the newer version into the table.

skywalking/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Component(Enum):
3636
RabbitmqProducer = 52
3737
RabbitmqConsumer = 53
3838
Elasticsearch = 47
39+
Urllib3 = 7006
3940

4041

4142
class Layer(Enum):

skywalking/plugins/sw_urllib3.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import logging
19+
20+
from skywalking import Layer, Component
21+
from skywalking.trace import tags
22+
from skywalking.trace.carrier import Carrier
23+
from skywalking.trace.context import get_context
24+
from skywalking.trace.tags import Tag
25+
26+
logger = logging.getLogger(__name__)
27+
28+
29+
def install():
30+
# noinspection PyBroadException
31+
try:
32+
from urllib3.request import RequestMethods
33+
34+
_request = RequestMethods.request
35+
36+
def _sw_request(this: RequestMethods, method, url, fields=None, headers=None, **urlopen_kw):
37+
38+
from urllib.parse import urlparse
39+
url_param = urlparse(url)
40+
carrier = Carrier()
41+
context = get_context()
42+
with context.new_exit_span(op=url_param.path or "/", peer=url_param.netloc, carrier=carrier) as span:
43+
span.layer = Layer.Http
44+
span.component = Component.Urllib3
45+
46+
if headers is None:
47+
headers = {}
48+
for item in carrier:
49+
headers[item.key] = item.val
50+
else:
51+
for item in carrier:
52+
headers[item.key] = item.val
53+
54+
try:
55+
res = _request(this, method, url, fields=fields, headers=headers, **urlopen_kw)
56+
57+
span.tag(Tag(key=tags.HttpMethod, val=method.upper()))
58+
span.tag(Tag(key=tags.HttpUrl, val=url))
59+
span.tag(Tag(key=tags.HttpStatus, val=res.status))
60+
if res.status >= 400:
61+
span.error_occurred = True
62+
except BaseException as e:
63+
span.raised()
64+
raise e
65+
return res
66+
67+
RequestMethods.request = _sw_request
68+
except Exception:
69+
logger.warning('failed to install plugin %s', __name__)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
version: '2.1'
19+
20+
services:
21+
collector:
22+
extends:
23+
service: collector
24+
file: ../docker/docker-compose.base.yml
25+
26+
provider:
27+
extends:
28+
service: agent
29+
file: ../docker/docker-compose.base.yml
30+
ports:
31+
- 9091:9091
32+
volumes:
33+
- .:/app
34+
command: ['bash', '-c', 'pip install flask && python3 /app/services/provider.py']
35+
depends_on:
36+
collector:
37+
condition: service_healthy
38+
healthcheck:
39+
test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091"]
40+
interval: 5s
41+
timeout: 60s
42+
retries: 120
43+
44+
consumer:
45+
extends:
46+
service: agent
47+
file: ../docker/docker-compose.base.yml
48+
ports:
49+
- 9090:9090
50+
volumes:
51+
- .:/app
52+
command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/consumer.py']
53+
depends_on:
54+
collector:
55+
condition: service_healthy
56+
provider:
57+
condition: service_healthy
58+
59+
networks:
60+
beyond:
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
19+
segmentItems:
20+
- serviceName: provider
21+
segmentSize: 1
22+
segments:
23+
- segmentId: not null
24+
spans:
25+
- operationName: /users
26+
operationId: 0
27+
parentSpanId: -1
28+
spanId: 0
29+
spanLayer: Http
30+
tags:
31+
- key: http.method
32+
value: POST
33+
- key: url
34+
value: http://provider:9091/users
35+
- key: status.code
36+
value: '200'
37+
refs:
38+
- parentEndpoint: /users
39+
networkAddress: 'provider:9091'
40+
refType: CrossProcess
41+
parentSpanId: 1
42+
parentTraceSegmentId: not null
43+
parentServiceInstance: not null
44+
parentService: consumer
45+
traceId: not null
46+
startTime: gt 0
47+
endTime: gt 0
48+
componentId: 7001
49+
spanType: Entry
50+
peer: not null
51+
skipAnalysis: false
52+
- serviceName: consumer
53+
segmentSize: 1
54+
segments:
55+
- segmentId: not null
56+
spans:
57+
- operationName: /users
58+
operationId: 0
59+
parentSpanId: 0
60+
spanId: 1
61+
spanLayer: Http
62+
startTime: gt 0
63+
endTime: gt 0
64+
componentId: 7006
65+
isError: false
66+
spanType: Exit
67+
peer: not null
68+
skipAnalysis: false
69+
tags:
70+
- key: http.method
71+
value: POST
72+
- key: url
73+
value: 'http://provider:9091/users'
74+
- key: status.code
75+
value: '200'
76+
- operationName: /users
77+
operationId: 0
78+
parentSpanId: -1
79+
spanId: 0
80+
spanLayer: Http
81+
startTime: gt 0
82+
endTime: gt 0
83+
componentId: 7001
84+
isError: false
85+
spanType: Entry
86+
peer: not null
87+
skipAnalysis: false
88+
tags:
89+
- key: http.method
90+
value: GET
91+
- key: url
92+
value: 'http://0.0.0.0:9090/users'
93+
- key: status.code
94+
value: '200'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
import json
18+
19+
from skywalking import agent, config
20+
21+
if __name__ == '__main__':
22+
config.service_name = 'consumer'
23+
config.logging_level = 'DEBUG'
24+
agent.start()
25+
26+
from flask import Flask, jsonify
27+
28+
app = Flask(__name__)
29+
import urllib3
30+
31+
@app.route("/users", methods=["POST", "GET"])
32+
def application():
33+
http = urllib3.PoolManager()
34+
res = http.request("POST", "http://provider:9091/users")
35+
36+
return jsonify(json.loads(res.data.decode('utf-8')))
37+
38+
PORT = 9090
39+
app.run(host='0.0.0.0', port=PORT, debug=True)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import time
19+
20+
from skywalking import agent, config
21+
22+
if __name__ == '__main__':
23+
config.service_name = 'provider'
24+
config.logging_level = 'DEBUG'
25+
agent.start()
26+
27+
from flask import Flask, jsonify
28+
29+
app = Flask(__name__)
30+
31+
@app.route("/users", methods=["POST", "GET"])
32+
def application():
33+
time.sleep(0.5)
34+
return jsonify({"song": "Despacito", "artist": "Luis Fonsi"})
35+
36+
PORT = 9091
37+
app.run(host='0.0.0.0', port=PORT, debug=True)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
from typing import Callable
18+
19+
import pytest
20+
import requests
21+
22+
from tests.plugin.base import TestPluginBase
23+
24+
25+
@pytest.fixture
26+
def prepare():
27+
# type: () -> Callable
28+
return lambda *_: requests.get('http://0.0.0.0:9090/users')
29+
30+
31+
class TestPlugin(TestPluginBase):
32+
@pytest.mark.parametrize('version', [
33+
'urllib3==1.25.10',
34+
'urllib3==1.25.9'
35+
])
36+
def test_plugin(self, docker_compose, version):
37+
self.validate()

0 commit comments

Comments
 (0)